Я делаю приложение, которое использует UITextView
. Теперь я хочу, чтобы в текстовом представлении был заполнитель, похожий на тот, который вы можете установить для текстового поля. Как бы вы достигли этого с помощью Swift?
Я делаю приложение, которое использует UITextView
. Теперь я хочу, чтобы в текстовом представлении был заполнитель, похожий на тот, который вы можете установить для текстового поля. Как бы вы достигли этого с помощью Swift?
Ответы:
Обновлено для Swift 4
UITextView
по сути не имеет свойства заполнителя, поэтому вам придется создавать и манипулировать им программно с помощью UITextViewDelegate
методов. Я рекомендую использовать решение № 1 или № 2 ниже в зависимости от желаемого поведения.
Примечание. Для любого решения добавьте UITextViewDelegate
в класс и установите textView.delegate = self
методы делегата текстового представления.
Решение № 1 - Если вы хотите, чтобы заполнитель исчез, как только пользователь выберет текстовое представление:
Сначала установите в UITextView
качестве текста-заполнителя и установите его в светло-серый цвет, чтобы имитировать внешний вид UITextField
текста-заполнителя. Либо сделайте это в viewDidLoad
или при создании текстового представления.
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
Затем, когда пользователь начинает редактировать текстовое представление, если текстовое представление содержит заполнитель (то есть, если его цвет текста является светло-серым), очистите текст заполнителя и установите цвет текста на черный, чтобы приспособить запись пользователя.
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.textColor == UIColor.lightGray {
textView.text = nil
textView.textColor = UIColor.black
}
}
Затем, когда пользователь заканчивает редактирование текстового представления и он уходит в отставку как первый респондент, если текстовое представление пусто, сбросьте его заполнитель, повторно добавив текст заполнителя и установив его цвет на светло-серый.
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
}
}
Решение № 2 - Если вы хотите, чтобы заполнитель отображался всякий раз, когда текстовое представление пусто, даже если текстовое представление выбрано:
Сначала установите заполнитель в viewDidLoad
:
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
textView.becomeFirstResponder()
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
(Примечание. Поскольку OP хотел, чтобы текстовое представление выбиралось сразу после загрузки представления, я включил выделение текстового представления в вышеприведенный код. Если это не ваше желаемое поведение и вы не хотите, чтобы текстовое представление выбиралось при загрузке представления, удалите две последние строки из указанного выше фрагмента кода.)
Затем используйте shouldChangeTextInRange
UITextViewDelegate
метод, например так:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
// Combine the textView text and the replacement text to
// create the updated text string
let currentText:String = textView.text
let updatedText = (currentText as NSString).replacingCharacters(in: range, with: text)
// If updated text view will be empty, add the placeholder
// and set the cursor to the beginning of the text view
if updatedText.isEmpty {
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
}
// Else if the text view's placeholder is showing and the
// length of the replacement string is greater than 0, set
// the text color to black then set its text to the
// replacement string
else if textView.textColor == UIColor.lightGray && !text.isEmpty {
textView.textColor = UIColor.black
textView.text = text
}
// For every other case, the text should change with the usual
// behavior...
else {
return true
}
// ...otherwise return false since the updates have already
// been made
return false
}
А также реализовать, textViewDidChangeSelection
чтобы пользователь не мог изменить положение курсора, пока заполнитель не виден. (Примечание: textViewDidChangeSelection
вызывается до загрузки вида, поэтому проверяйте цвет текста только в том случае, если окно видно):
func textViewDidChangeSelection(_ textView: UITextView) {
if self.view.window != nil {
if textView.textColor == UIColor.lightGray {
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
}
}
}
yourTextField.delegate = self
. Если вы не сделаете этого, textViewDidBeginEditing
и textViewDidEndEditing
функции не будут работать.
Cannot convert value of type 'NSRange' (aka '_NSRange') to expected argument type 'Range<String.Index>' (aka 'Range<String.CharacterView.Index>')
.
let currentText = textView.text as NSString?
. Преобразуйте let updatedText =
строку в let updatedText = currentText?.replacingCharacters(in: range, with: text)
. Наконец, преобразуйте if updatedText.isEmpty
строку в if (updatedText?.isEmpty)! {
. Это должно делать свое дело!
textView.selectedTextRange
изнутри func textViewDidChangeSelection(_ textView: UITextView)
вызывает бесконечный цикл ...
Это просто, безопасно и надежно расположить метку-заполнитель над текстовым представлением, установить его шрифт, цвет и управлять видимостью-заполнителем, отслеживая изменения количества символов в текстовом представлении.
Свифт 3:
class NotesViewController : UIViewController, UITextViewDelegate {
@IBOutlet var textView : UITextView!
var placeholderLabel : UILabel!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
placeholderLabel = UILabel()
placeholderLabel.text = "Enter some text..."
placeholderLabel.font = UIFont.italicSystemFont(ofSize: (textView.font?.pointSize)!)
placeholderLabel.sizeToFit()
textView.addSubview(placeholderLabel)
placeholderLabel.frame.origin = CGPoint(x: 5, y: (textView.font?.pointSize)! / 2)
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.isHidden = !textView.text.isEmpty
}
func textViewDidChange(_ textView: UITextView) {
placeholderLabel.isHidden = !textView.text.isEmpty
}
}
Swift 2: То же самое, за исключением: italicSystemFontOfSize(textView.font.pointSize)
,UIColor.lightGrayColor
Настоятельно рекомендуем использовать библиотеку KMPlaceholderTextView . Очень прост в использовании.
Swift:
Добавьте текстовое представление программно или через Интерфейсный Разработчик, если последнее, создайте выход:
@IBOutlet weak var yourTextView: UITextView!
Пожалуйста, добавьте делегата (UITextViewDelegate):
class ViewController: UIViewController, UITextViewDelegate {
В методе viewDidLoad добавьте следующее:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
yourTextView.delegate = self
yourTextView.text = "Placeholder text goes right here..."
yourTextView.textColor = UIColor.lightGray
Теперь позвольте мне представить магическую часть, добавив эту функцию:
func textViewDidBeginEditing(_ textView: UITextView) {
if yourTextView.textColor == UIColor.lightGray {
yourTextView.text = ""
yourTextView.textColor = UIColor.black
}
}
Обратите внимание, что это будет выполняться всякий раз, когда начинается редактирование, там мы проверим условия, чтобы сообщить состояние, используя свойство color. Задавать текст nil
я не рекомендую. Сразу после этого мы устанавливаем желаемый цвет текста, в данном случае черный.
Теперь добавьте эту функцию тоже:
func textViewDidEndEditing(_ textView: UITextView) {
if yourTextView.text == "" {
yourTextView.text = "Placeholder text ..."
yourTextView.textColor = UIColor.lightGray
}
}
Позвольте мне настаивать, не сравнить с nil
, я уже попробовал это, и это не будет работать. Затем мы устанавливаем значения обратно в стиль заполнителя и устанавливаем цвет обратно в цвет заполнителя, потому что это условие для регистрации textViewDidBeginEditing
.
Используйте это расширение, это лучший способ установить заполнитель в UITextView. Но убедитесь, что вы прикрепили делегатов к TextView. Вы можете установить Placeholder следующим образом: -
yourTextView.placeholder = "Placeholder"
extension UITextView :UITextViewDelegate
{
/// Resize the placeholder when the UITextView bounds change
override open var bounds: CGRect {
didSet {
self.resizePlaceholder()
}
}
/// The UITextView placeholder text
public var placeholder: String? {
get {
var placeholderText: String?
if let placeholderLabel = self.viewWithTag(100) as? UILabel {
placeholderText = placeholderLabel.text
}
return placeholderText
}
set {
if let placeholderLabel = self.viewWithTag(100) as! UILabel? {
placeholderLabel.text = newValue
placeholderLabel.sizeToFit()
} else {
self.addPlaceholder(newValue!)
}
}
}
/// When the UITextView did change, show or hide the label based on if the UITextView is empty or not
///
/// - Parameter textView: The UITextView that got updated
public func textViewDidChange(_ textView: UITextView) {
if let placeholderLabel = self.viewWithTag(100) as? UILabel {
placeholderLabel.isHidden = self.text.characters.count > 0
}
}
/// Resize the placeholder UILabel to make sure it's in the same position as the UITextView text
private func resizePlaceholder() {
if let placeholderLabel = self.viewWithTag(100) as! UILabel? {
let labelX = self.textContainer.lineFragmentPadding
let labelY = self.textContainerInset.top - 2
let labelWidth = self.frame.width - (labelX * 2)
let labelHeight = placeholderLabel.frame.height
placeholderLabel.frame = CGRect(x: labelX, y: labelY, width: labelWidth, height: labelHeight)
}
}
/// Adds a placeholder UILabel to this UITextView
private func addPlaceholder(_ placeholderText: String) {
let placeholderLabel = UILabel()
placeholderLabel.text = placeholderText
placeholderLabel.sizeToFit()
placeholderLabel.font = self.font
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.tag = 100
placeholderLabel.isHidden = self.text.characters.count > 0
self.addSubview(placeholderLabel)
self.resizePlaceholder()
self.delegate = self
}
}
Я удивлен, что никто не упомянул NSTextStorageDelegate
. UITextViewDelegate
Методы будут вызваны только взаимодействием с пользователем, но не программно. Например, когда вы устанавливаете text
свойство текстового представления программно, вам придется самостоятельно устанавливать видимость заполнителя, потому что методы делегата не будут вызываться.
Однако, NSTextStorageDelegate
«S textStorage(_:didProcessEditing:range:changeInLength:)
метод, вы будете уведомлены о любых изменениях в тексте, даже если это делается программно. Просто назначьте это так:
textView.textStorage.delegate = self
(В UITextView
этом свойстве делегата используется nil
по умолчанию, поэтому оно не влияет на поведение по умолчанию.)
Объедините его с UILabel
техникой @clearlight демонстрирует, можно легко обернуть всю UITextView
«s placeholder
реализации в расширение.
extension UITextView {
private class PlaceholderLabel: UILabel { }
private var placeholderLabel: PlaceholderLabel {
if let label = subviews.compactMap( { $0 as? PlaceholderLabel }).first {
return label
} else {
let label = PlaceholderLabel(frame: .zero)
label.font = font
addSubview(label)
return label
}
}
@IBInspectable
var placeholder: String {
get {
return subviews.compactMap( { $0 as? PlaceholderLabel }).first?.text ?? ""
}
set {
let placeholderLabel = self.placeholderLabel
placeholderLabel.text = newValue
placeholderLabel.numberOfLines = 0
let width = frame.width - textContainer.lineFragmentPadding * 2
let size = placeholderLabel.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
placeholderLabel.frame.size.height = size.height
placeholderLabel.frame.size.width = width
placeholderLabel.frame.origin = CGPoint(x: textContainer.lineFragmentPadding, y: textContainerInset.top)
textStorage.delegate = self
}
}
}
extension UITextView: NSTextStorageDelegate {
public func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {
if editedMask.contains(.editedCharacters) {
placeholderLabel.isHidden = !text.isEmpty
}
}
}
Обратите внимание, что использование закрытого (вложенного) класса называется PlaceholderLabel
. У него вообще нет реализации, но он дает нам способ идентифицировать метку-заполнитель, которая гораздо более «быстрая», чем использование tag
свойства.
При таком подходе вы все равно можете назначить делегата UITextView
кому-либо еще.
Вам даже не нужно менять классы ваших текстовых представлений. Просто добавьте расширение (я), и вы сможете назначить строку-заполнитель каждому UITextView
в вашем проекте, даже в Интерфейсном Разработчике.
Я упустил реализацию placeholderColor
свойства для ясности, но оно может быть реализовано всего за несколько строк с аналогичной вычисляемой переменной placeholder
.
textView.textStorage.delegate = self
это в контроллере представления потребует, чтобы мы связали тот контроллер представления с NSTextStorageDelegate
. Это действительно требует?
NSTextStorageDelegate
, а не контроллер представления.
Я сделал это с помощью двух разных текстовых представлений:
Идея состоит в том, что, как только пользователь начинает набирать текст на переднем плане, заполнитель на заднем плане исчезает (и появляется снова, если пользователь удаляет все). Таким образом, он ведет себя как заполнитель для однострочного текстового поля.
Вот код, который я использовал для этого. Обратите внимание, что descriptionField - это поле, которое вводит пользователь, а descriptionPlaceholder - это поле в фоновом режиме.
func textViewDidChange(descriptionField: UITextView) {
if descriptionField.text.isEmpty == false {
descriptionPlaceholder.text = ""
} else {
descriptionPlaceholder.text = descriptionPlaceholderText
}
}
Основываясь на некоторых замечательных предложениях, я смог собрать следующий легкий подкласс, совместимый с Interface-Builder UITextView
, который:
UITextField
.text
свойство поля .Любые предложения по улучшению приветствуются, особенно если есть какой-либо способ получить цвет заполнителя iOS программно, а не жестко программировать его.
Swift v5:
import UIKit
@IBDesignable class TextViewWithPlaceholder: UITextView {
override var text: String! { // Ensures that the placeholder text is never returned as the field's text
get {
if showingPlaceholder {
return "" // When showing the placeholder, there's no real text to return
} else { return super.text }
}
set { super.text = newValue }
}
@IBInspectable var placeholderText: String = ""
@IBInspectable var placeholderTextColor: UIColor = UIColor(red: 0.78, green: 0.78, blue: 0.80, alpha: 1.0) // Standard iOS placeholder color (#C7C7CD). See /programming/31057746/whats-the-default-color-for-placeholder-text-in-uitextfield
private var showingPlaceholder: Bool = true // Keeps track of whether the field is currently showing a placeholder
override func didMoveToWindow() {
super.didMoveToWindow()
if text.isEmpty {
showPlaceholderText() // Load up the placeholder text when first appearing, but not if coming back to a view where text was already entered
}
}
override func becomeFirstResponder() -> Bool {
// If the current text is the placeholder, remove it
if showingPlaceholder {
text = nil
textColor = nil // Put the text back to the default, unmodified color
showingPlaceholder = false
}
return super.becomeFirstResponder()
}
override func resignFirstResponder() -> Bool {
// If there's no text, put the placeholder back
if text.isEmpty {
showPlaceholderText()
}
return super.resignFirstResponder()
}
private func showPlaceholderText() {
showingPlaceholder = true
textColor = placeholderTextColor
text = placeholderText
}
}
УСТАНОВИТЬ значение в представлении нагрузки
txtVw!.autocorrectionType = UITextAutocorrectionType.No
txtVw!.text = "Write your Placeholder"
txtVw!.textColor = UIColor.lightGrayColor()
func textViewDidBeginEditing(textView: UITextView) {
if (txtVw?.text == "Write your Placeholder")
{
txtVw!.text = nil
txtVw!.textColor = UIColor.blackColor()
}
}
func textViewDidEndEditing(textView: UITextView) {
if txtVw!.text.isEmpty
{
txtVw!.text = "Write your Placeholder"
txtVw!.textColor = UIColor.lightGrayColor()
}
textView.resignFirstResponder()
}
Я пытался сделать код удобным из Clearlight «s ответа .
extension UITextView{
func setPlaceholder() {
let placeholderLabel = UILabel()
placeholderLabel.text = "Enter some text..."
placeholderLabel.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
placeholderLabel.sizeToFit()
placeholderLabel.tag = 222
placeholderLabel.frame.origin = CGPoint(x: 5, y: (self.font?.pointSize)! / 2)
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.isHidden = !self.text.isEmpty
self.addSubview(placeholderLabel)
}
func checkPlaceholder() {
let placeholderLabel = self.viewWithTag(222) as! UILabel
placeholderLabel.isHidden = !self.text.isEmpty
}
}
использование
override func viewDidLoad() {
textView.delegate = self
textView.setPlaceholder()
}
func textViewDidChange(_ textView: UITextView) {
textView.checkPlaceholder()
}
Еще одно решение (Swift 3):
import UIKit
protocol PlaceholderTextViewDelegate {
func placeholderTextViewDidChangeText(_ text:String)
func placeholderTextViewDidEndEditing(_ text:String)
}
final class PlaceholderTextView: UITextView {
var notifier:PlaceholderTextViewDelegate?
var placeholder: String? {
didSet {
placeholderLabel?.text = placeholder
}
}
var placeholderColor = UIColor.lightGray
var placeholderFont = UIFont.appMainFontForSize(14.0) {
didSet {
placeholderLabel?.font = placeholderFont
}
}
fileprivate var placeholderLabel: UILabel?
// MARK: - LifeCycle
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
NotificationCenter.default.addObserver(self, selector: #selector(PlaceholderTextView.textDidChangeHandler(notification:)), name: .UITextViewTextDidChange, object: nil)
placeholderLabel = UILabel()
placeholderLabel?.textColor = placeholderColor
placeholderLabel?.text = placeholder
placeholderLabel?.textAlignment = .left
placeholderLabel?.numberOfLines = 0
}
override func layoutSubviews() {
super.layoutSubviews()
placeholderLabel?.font = placeholderFont
var height:CGFloat = placeholderFont.lineHeight
if let data = placeholderLabel?.text {
let expectedDefaultWidth:CGFloat = bounds.size.width
let fontSize:CGFloat = placeholderFont.pointSize
let textView = UITextView()
textView.text = data
textView.font = UIFont.appMainFontForSize(fontSize)
let sizeForTextView = textView.sizeThatFits(CGSize(width: expectedDefaultWidth,
height: CGFloat.greatestFiniteMagnitude))
let expectedTextViewHeight = sizeForTextView.height
if expectedTextViewHeight > height {
height = expectedTextViewHeight
}
}
placeholderLabel?.frame = CGRect(x: 5, y: 0, width: bounds.size.width - 16, height: height)
if text.isEmpty {
addSubview(placeholderLabel!)
bringSubview(toFront: placeholderLabel!)
} else {
placeholderLabel?.removeFromSuperview()
}
}
func textDidChangeHandler(notification: Notification) {
layoutSubviews()
}
}
extension PlaceholderTextView : UITextViewDelegate {
// MARK: - UITextViewDelegate
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
func textViewDidChange(_ textView: UITextView) {
notifier?.placeholderTextViewDidChangeText(textView.text)
}
func textViewDidEndEditing(_ textView: UITextView) {
notifier?.placeholderTextViewDidEndEditing(textView.text)
}
}
результат
Простое и быстрое решение, которое работает для меня:
@IBDesignable
class PlaceHolderTextView: UITextView {
@IBInspectable var placeholder: String = "" {
didSet{
updatePlaceHolder()
}
}
@IBInspectable var placeholderColor: UIColor = UIColor.gray {
didSet {
updatePlaceHolder()
}
}
private var originalTextColor = UIColor.darkText
private var originalText: String = ""
private func updatePlaceHolder() {
if self.text == "" || self.text == placeholder {
self.text = placeholder
self.textColor = placeholderColor
if let color = self.textColor {
self.originalTextColor = color
}
self.originalText = ""
} else {
self.textColor = self.originalTextColor
self.originalText = self.text
}
}
override func becomeFirstResponder() -> Bool {
let result = super.becomeFirstResponder()
self.text = self.originalText
self.textColor = self.originalTextColor
return result
}
override func resignFirstResponder() -> Bool {
let result = super.resignFirstResponder()
updatePlaceHolder()
return result
}
}
Вот что я использую для этой работы.
@IBDesignable class UIPlaceholderTextView: UITextView {
var placeholderLabel: UILabel?
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
sharedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedInit()
}
override func prepareForInterfaceBuilder() {
sharedInit()
}
func sharedInit() {
refreshPlaceholder()
NotificationCenter.default.addObserver(self, selector: #selector(textChanged), name: UITextView.textDidChangeNotification, object: nil)
}
@IBInspectable var placeholder: String? {
didSet {
refreshPlaceholder()
}
}
@IBInspectable var placeholderColor: UIColor? = .darkGray {
didSet {
refreshPlaceholder()
}
}
@IBInspectable var placeholderFontSize: CGFloat = 14 {
didSet {
refreshPlaceholder()
}
}
func refreshPlaceholder() {
if placeholderLabel == nil {
placeholderLabel = UILabel()
let contentView = self.subviews.first ?? self
contentView.addSubview(placeholderLabel!)
placeholderLabel?.translatesAutoresizingMaskIntoConstraints = false
placeholderLabel?.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: textContainerInset.left + 4).isActive = true
placeholderLabel?.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: textContainerInset.right + 4).isActive = true
placeholderLabel?.topAnchor.constraint(equalTo: contentView.topAnchor, constant: textContainerInset.top).isActive = true
placeholderLabel?.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: textContainerInset.bottom)
}
placeholderLabel?.text = placeholder
placeholderLabel?.textColor = placeholderColor
placeholderLabel?.font = UIFont.systemFont(ofSize: placeholderFontSize)
}
@objc func textChanged() {
if self.placeholder?.isEmpty ?? true {
return
}
UIView.animate(withDuration: 0.25) {
if self.text.isEmpty {
self.placeholderLabel?.alpha = 1.0
} else {
self.placeholderLabel?.alpha = 0.0
}
}
}
override var text: String! {
didSet {
textChanged()
}
}
}
Я знаю, что есть несколько подходов, подобных этому, но преимущества этого:
Swift 3.2
extension EditProfileVC:UITextViewDelegate{
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.textColor == UIColor.lightGray {
textView.text = nil
textView.textColor = UIColor.black
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
}
}
}
Сначала, когда пользователь начинает редактировать вызов textViewDidBeginEditing, а затем проверяет, означает ли цвет текста серый, означает, что пользователь ничего не писал, тогда установите значение textview nil и измените цвет на черный для текстовых сообщений пользователя.
Когда пользователь завершает редактирование textViewDidEndEditing, вызывается и проверяется, не пишет ли пользователь ничего в textview, тогда текст устанавливается серым цветом с текстом «PlaceHolder»
Вот мой способ решения этой проблемы ( Swift 4 ):
Идея заключалась в том, чтобы сделать самое простое из возможных решений, которое позволяет использовать заполнители разных цветов, изменять размеры до размера заполнителей, не перезаписывая при delegate
этом все UITextView
функции, работающие должным образом.
import UIKit
class PlaceholderTextView: UITextView {
var placeholderColor: UIColor = .lightGray
var defaultTextColor: UIColor = .black
private var isShowingPlaceholder = false {
didSet {
if isShowingPlaceholder {
text = placeholder
textColor = placeholderColor
} else {
textColor = defaultTextColor
}
}
}
var placeholder: String? {
didSet {
isShowingPlaceholder = !hasText
}
}
@objc private func textViewDidBeginEditing(notification: Notification) {
textColor = defaultTextColor
if isShowingPlaceholder { text = nil }
}
@objc private func textViewDidEndEditing(notification: Notification) {
isShowingPlaceholder = !hasText
}
// MARK: - Construction -
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
NotificationCenter.default.addObserver(self, selector: #selector(textViewDidBeginEditing(notification:)), name: UITextView.textDidBeginEditingNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(textViewDidEndEditing(notification:)), name: UITextView.textDidEndEditingNotification, object: nil)
}
// MARK: - Destruction -
deinit { NotificationCenter.default.removeObserver(self) }
}
Я не знаю, почему люди слишком усложняют эту проблему ... Это довольно просто и понятно. Вот подкласс UITextView, который обеспечивает запрошенную функциональность.
- (void)customInit
{
self.contentMode = UIViewContentModeRedraw;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
}
- (void)textChanged:(NSNotification *)notification
{
if (notification.object == self) {
if(self.textStorage.length != 0 || !self.textStorage.length) {
[self setNeedsDisplay];
}
}
}
#pragma mark - Setters
- (void)setPlaceholderText:(NSString *)placeholderText withFont:(UIFont *)font
{
self.placeholderText = placeholderText;
self.placeholderTextFont = font;
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
[[UIColor lightGrayColor] setFill];
if (self.textStorage.length != 0) {
return;
}
CGRect inset = CGRectInset(rect, 8, 8);//Default rect insets for textView
NSDictionary *attributes = @{NSFontAttributeName: self.placeholderTextFont, NSForegroundColorAttributeName: [UIColor grayColor]};
[self.placeholderText drawInRect:inset withAttributes:attributes];
}`
Это мое готовое решение, если вы работаете с несколькими текстовыми представлениями.
func textViewShouldBeginEditing(textView: UITextView) -> Bool {
// Set cursor to the beginning if placeholder is set
if textView.textColor == UIColor.lightGrayColor() {
textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
}
return true
}
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
// Remove placeholder
if textView.textColor == UIColor.lightGrayColor() && text.characters.count > 0 {
textView.text = ""
textView.textColor = UIColor.blackColor()
}
if text == "\n" {
textView.resignFirstResponder()
return false
}
return true
}
func textViewDidChange(textView: UITextView) {
// Set placeholder if text is empty
if textView.text.isEmpty {
textView.text = NSLocalizedString("Hint", comment: "hint")
textView.textColor = UIColor.lightGrayColor()
textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
}
}
func textViewDidChangeSelection(textView: UITextView) {
// Set cursor to the beginning if placeholder is set
let firstPosition = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
// Do not change position recursively
if textView.textColor == UIColor.lightGrayColor() && textView.selectedTextRange != firstPosition {
textView.selectedTextRange = firstPosition
}
}
Swift 3.1
Это расширение хорошо сработало для меня: https://github.com/devxoul/UITextView-Placeholder
Вот фрагмент кода:
Установите его через модуль:
pod 'UITextView+Placeholder', '~> 1.2'
Импортируйте его в свой класс
import UITextView_Placeholder
И добавьте placeholder
свойство к уже созданномуUITextView
textView.placeholder = "Put some detail"
Вот так ... Вот как это выглядит (Третья коробка UITextView
)
В отличие от почти каждый ответ на этот пост, UITextView
делает имеют свойство заполнитель. По причинам вне моего понимания, это раскрывается только в IB, как таковой:
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="placeholder" value="My Placeholder"/>
</userDefinedRuntimeAttributes>
Поэтому, если вы используете раскадровки и статического заполнителя будет достаточно, просто установите свойство в инспекторе.
Вы также можете установить это свойство в коде так:
textView.setValue("My Placeholder", forKeyPath: "placeholder")
Его мутный , как к погоде это доступ через частный API, как свойство является подвергается.
Я не пытался отправить с этим методом. Но я отправлю этот способ в ближайшее время и обновлю этот ответ соответственно.
ОБНОВИТЬ:
Я отправил этот код в нескольких выпусках без проблем от Apple.
ОБНОВЛЕНИЕ: Это будет работать только в Xcode pre 11.2
UITextField
НЕ, UITextView
пожалуйста, прочитайте вопросы / ответы более внимательно.
В ios нет такого свойства для добавления заполнителя непосредственно в TextView, вместо этого вы можете добавить метку и показать / скрыть изменение в textView. SWIFT 2.0 и убедитесь, что для реализации textviewdelegate
func textViewDidChange(TextView: UITextView)
{
if txtShortDescription.text == ""
{
self.lblShortDescription.hidden = false
}
else
{
self.lblShortDescription.hidden = true
}
}
Swift - я написал класс, который унаследовал UITextView, и добавил UILabel в качестве подпредставления, чтобы действовать в качестве заполнителя.
import UIKit
@IBDesignable
class HintedTextView: UITextView {
@IBInspectable var hintText: String = "hintText" {
didSet{
hintLabel.text = hintText
}
}
private lazy var hintLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFontOfSize(16)
label.textColor = UIColor.lightGrayColor()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupView()
}
private func setupView() {
translatesAutoresizingMaskIntoConstraints = false
delegate = self
font = UIFont.systemFontOfSize(16)
addSubview(hintLabel)
NSLayoutConstraint.activateConstraints([
hintLabel.leftAnchor.constraintEqualToAnchor(leftAnchor, constant: 4),
hintLabel.rightAnchor.constraintEqualToAnchor(rightAnchor, constant: 8),
hintLabel.topAnchor.constraintEqualToAnchor(topAnchor, constant: 4),
hintLabel.heightAnchor.constraintEqualToConstant(30)
])
}
override func layoutSubviews() {
super.layoutSubviews()
setupView()
}
}
Мне нравится решение @ nerdist. Исходя из этого, я создал расширение для UITextView
:
import Foundation
import UIKit
extension UITextView
{
private func add(_ placeholder: UILabel) {
for view in self.subviews {
if let lbl = view as? UILabel {
if lbl.text == placeholder.text {
lbl.removeFromSuperview()
}
}
}
self.addSubview(placeholder)
}
func addPlaceholder(_ placeholder: UILabel?) {
if let ph = placeholder {
ph.numberOfLines = 0 // support for multiple lines
ph.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
ph.sizeToFit()
self.add(ph)
ph.frame.origin = CGPoint(x: 5, y: (self.font?.pointSize)! / 2)
ph.textColor = UIColor(white: 0, alpha: 0.3)
updateVisibility(ph)
}
}
func updateVisibility(_ placeHolder: UILabel?) {
if let ph = placeHolder {
ph.isHidden = !self.text.isEmpty
}
}
}
Например, в классе ViewController я так его использую:
class MyViewController: UIViewController, UITextViewDelegate {
private var notePlaceholder: UILabel!
@IBOutlet weak var txtNote: UITextView!
...
// UIViewController
override func viewDidLoad() {
notePlaceholder = UILabel()
notePlaceholder.text = "title\nsubtitle\nmore..."
txtNote.addPlaceholder(notePlaceholder)
...
}
// UITextViewDelegate
func textViewDidChange(_ textView: UITextView) {
txtNote.updateVisbility(notePlaceholder)
...
}
Заполнитель на UITextview!
ОБНОВИТЬ :
Если вы изменяете текст textview в коде, не забудьте вызвать метод updateVisibitly, чтобы скрыть заполнитель:
txtNote.text = "something in code"
txtNote.updateVisibility(self.notePlaceholder) // hide placeholder if text is not empty.
Чтобы запретить добавление заполнителя более одного раза, add()
в extension
.
В swift2.2:
public class CustomTextView: UITextView {
private struct Constants {
static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
}
private let placeholderLabel: UILabel = UILabel()
private var placeholderLabelConstraints = [NSLayoutConstraint]()
@IBInspectable public var placeholder: String = "" {
didSet {
placeholderLabel.text = placeholder
}
}
@IBInspectable public var placeholderColor: UIColor = CustomTextView.Constants.defaultiOSPlaceholderColor {
didSet {
placeholderLabel.textColor = placeholderColor
}
}
override public var font: UIFont! {
didSet {
placeholderLabel.font = font
}
}
override public var textAlignment: NSTextAlignment {
didSet {
placeholderLabel.textAlignment = textAlignment
}
}
override public var text: String! {
didSet {
textDidChange()
}
}
override public var attributedText: NSAttributedString! {
didSet {
textDidChange()
}
}
override public 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() {
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(textDidChange),
name: UITextViewTextDidChangeNotification,
object: nil)
placeholderLabel.font = font
placeholderLabel.textColor = placeholderColor
placeholderLabel.textAlignment = textAlignment
placeholderLabel.text = placeholder
placeholderLabel.numberOfLines = 0
placeholderLabel.backgroundColor = UIColor.clearColor()
placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(placeholderLabel)
updateConstraintsForPlaceholderLabel()
}
private func updateConstraintsForPlaceholderLabel() {
var newConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|-(\(textContainerInset.left + textContainer.lineFragmentPadding))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints += NSLayoutConstraint.constraintsWithVisualFormat("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.hidden = !text.isEmpty
}
public override func layoutSubviews() {
super.layoutSubviews()
placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self,
name: UITextViewTextDidChangeNotification,
object: nil)
}
}
В swift3:
import UIKit
Класс CustomTextView: UITextView {
private struct Constants {
static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
}
private let placeholderLabel: UILabel = UILabel()
private var placeholderLabelConstraints = [NSLayoutConstraint]()
@IBInspectable public var placeholder: String = "" {
didSet {
placeholderLabel.text = placeholder
}
}
@IBInspectable public var placeholderColor: UIColor = CustomTextView.Constants.defaultiOSPlaceholderColor {
didSet {
placeholderLabel.textColor = placeholderColor
}
}
override public var font: UIFont! {
didSet {
placeholderLabel.font = font
}
}
override public var textAlignment: NSTextAlignment {
didSet {
placeholderLabel.textAlignment = textAlignment
}
}
override public var text: String! {
didSet {
textDidChange()
}
}
override public var attributedText: NSAttributedString! {
didSet {
textDidChange()
}
}
override public 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() {
NotificationCenter.default.addObserver(self,
selector: #selector(textDidChange),
name: NSNotification.Name.UITextViewTextDidChange,
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
}
public override func layoutSubviews() {
super.layoutSubviews()
placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
}
deinit {
NotificationCenter.default.removeObserver(self,
name: NSNotification.Name.UITextViewTextDidChange,
object: nil)
}
}
Я написал класс по быстрому. Вам нужно импортировать этот класс, когда это необходимо.
Я не могу добавить комментарий из-за репутации. добавьте еще одного делегата в ответ @clearlight.
func textViewDidBeginEditing(_ textView: UITextView) {
cell.placeholderLabel.isHidden = !textView.text.isEmpty
}
это нужно
потому что textViewDidChange
не называется первый раз
Нет, там нет места для текста. Вы должны поставить метку над ним, когда пользователь вводит текстовое представление, затем скрыть его или установить значение по умолчанию, когда пользователь вводит, удалить все значения.
func setPlaceholder(){
var placeholderLabel = UILabel()
placeholderLabel.text = "Describe your need..."
placeholderLabel.font = UIFont.init(name: "Lato-Regular", size: 15.0) ?? UIFont.boldSystemFont(ofSize: 14.0)
placeholderLabel.sizeToFit()
descriptionTextView.addSubview(placeholderLabel)
placeholderLabel.frame.origin = CGPoint(x: 5, y: (descriptionTextView.font?.pointSize)! / 2)
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.isHidden = !descriptionTextView.text.isEmpty
}
//Delegate Method.
func textViewDidChange(_ textView: UITextView) {
placeholderLabel.isHidden = !textView.text.isEmpty
}
Мне пришлось отправить очередь, чтобы текст заполнителя снова появился после завершения редактирования.
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.text == "Description" {
textView.text = nil
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
DispatchQueue.main.async {
textView.text = "Description"
}
}
}
Swift:
Добавьте свой TextView
@IBOutlet
:
@IBOutlet weak var txtViewMessage: UITextView!
В viewWillAppear
метод добавьте следующее:
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
txtViewMessage.delegate = self // Give TextViewMessage delegate Method
txtViewMessage.text = "Place Holder Name"
txtViewMessage.textColor = UIColor.lightGray
}
Пожалуйста, добавьте Delegate
расширение Using (UITextViewDelegate):
// MARK: - UITextViewDelegate
extension ViewController: UITextViewDelegate
{
func textViewDidBeginEditing(_ textView: UITextView)
{
if !txtViewMessage.text!.isEmpty && txtViewMessage.text! == "Place Holder Name"
{
txtViewMessage.text = ""
txtViewMessage.textColor = UIColor.black
}
}
func textViewDidEndEditing(_ textView: UITextView)
{
if txtViewMessage.text.isEmpty
{
txtViewMessage.text = "Place Holder Name"
txtViewMessage.textColor = UIColor.lightGray
}
}
}
Наше решение позволяет избежать использования свойств UITextView
text
и textColor
свойств, что удобно, если вы ведете счетчик символов.
Это просто:
1) Создайте пустышку UITextView
в раскадровке с теми же свойствами, что и у мастера UITextView
. Присвойте замещающий текст пустому тексту.
2) Совместите верхний, левый и правый края двух UITextViews.
3) Поместите манекен позади мастера.
4) Переопределите textViewDidChange(textView:)
функцию делегата мастера и покажите манекен, если мастер имеет 0 символов. В противном случае покажи мастера.
Это предполагает, что оба UITextViews
имеют прозрачный фон. Если это не так, поместите манекен сверху, когда есть 0 символов, и вставьте его внизу, если> 0 символов. Вам также придется поменять местами респонденты, чтобы убедиться, что курсор следует вправо UITextView
.
Свифт 4, 4.2 и 5
[![@IBOutlet var detailTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
detailTextView.delegate = self
}
extension ContactUsViewController : UITextViewDelegate {
public func textViewDidBeginEditing(_ textView: UITextView) {
if textView.text == "Write your message here..." {
detailTextView.text = ""
detailTextView.textColor = UIColor.init(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.86)
}
textView.becomeFirstResponder()
}
public func textViewDidEndEditing(_ textView: UITextView) {
if textView.text == "" {
detailTextView.text = "Write your message here..."
detailTextView.textColor = UIColor.init(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.30)
}
textView.resignFirstResponder()
}
[![}][1]][1]