Swift 中 UITextView 内的占位符文本
Placeholder text inside UITextView in Swift
我做了一些研究,发现了这个 post:Add placeholder text inside UITextView in Swift?
将上面的示例合并到我的代码中,我在空白 xcode UIKit 项目中有以下内容:
import UIKit
class ViewController: UIViewController {
var sampleTextView = UITextView()
let placeholderText = "Type Something"
let placeholderTextColor = UIColor.lightGray
let normalTextColor = UIColor.label
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray
sampleTextView.delegate = self
sampleTextView.text = placeholderText
sampleTextView.textColor = placeholderTextColor
sampleTextView.becomeFirstResponder()
sampleTextView.selectedTextRange = sampleTextView.textRange(from: sampleTextView.beginningOfDocument, to: sampleTextView.beginningOfDocument)
view.addSubview(sampleTextView)
sampleTextView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
sampleTextView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0),
sampleTextView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
sampleTextView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10),
sampleTextView.heightAnchor.constraint(equalToConstant: 100)
])
}
}
extension ViewController: UITextViewDelegate {
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = placeholderText
textView.textColor = placeholderTextColor
}
}
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 = placeholderText
textView.textColor = placeholderTextColor
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 == placeholderTextColor && !text.isEmpty {
textView.textColor = normalTextColor
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
}
func textViewDidChangeSelection(_ textView: UITextView) {
if self.view.window != nil {
if textView.textColor == placeholderTextColor {
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
}
}
}
}
但我有两个错误,我无法解决这个问题:
- 这首先是使用键盘工具栏上方的预测文本。重复第一个选定的作品 - 请参见下面的屏幕截图:
- 如果键入,第一个字母按预期按下键盘的 SHIFT 键,但第二个字母也保持按下状态 - 请参见下面的屏幕截图:
抱歉,如果这些是基本的,但我很难过。
情侣笔记...
我认为 Shift 没有被释放,因为你 return False
来自 shouldChangeTextIn range
...所以 textView 没有完全与键盘沟通。
至于重复的预测文本...我 运行 遇到过类似的问题。我的印象是文本 已经插入 并且在调用 shouldChangeTextIn range
时设置了新的 .selectedRange
。所以代码(如所写)复制了它。
由于这些(和其他)复杂性,这里有一个使用 CATextLayer
作为“占位符”文本的示例:
class PlaceHolderTestViewController: UIViewController, UITextViewDelegate {
var sampleTextView = UITextView()
let placeholderText = "Type Something"
let placeholderTextColor = UIColor.lightGray
let normalTextColor = UIColor.label
let textLayer = CATextLayer()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray
if self.navigationController != nil {
// add a "Done" navigation bar button
let btn = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTap))
self.navigationItem.rightBarButtonItem = btn
}
view.addSubview(sampleTextView)
sampleTextView.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
sampleTextView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40),
sampleTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 10),
sampleTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -10),
sampleTextView.heightAnchor.constraint(equalToConstant: 100)
])
sampleTextView.font = .systemFont(ofSize: 16.0)
// textLayer properties
textLayer.contentsScale = UIScreen.main.scale
textLayer.alignmentMode = .left
textLayer.isWrapped = true
textLayer.foregroundColor = placeholderTextColor.cgColor
textLayer.string = placeholderText
if let fnt = sampleTextView.font {
textLayer.fontSize = fnt.pointSize
} else {
textLayer.fontSize = 12.0
}
// insert the textLayer
sampleTextView.layer.insertSublayer(textLayer, at: 0)
// set delegate to self
sampleTextView.delegate = self
}
func textViewDidChange(_ textView: UITextView) {
textLayer.opacity = textView.text.isEmpty ? 1.0 : 0.0
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// update textLayer frame here
textLayer.frame = sampleTextView.bounds.insetBy(
dx: sampleTextView.textContainerInset.left + sampleTextView.textContainer.lineFragmentPadding,
dy: sampleTextView.textContainerInset.top
)
}
@objc func doneTap() -> Void {
view.endEditing(true)
}
}
并且,这里有一个子类化 UITextView
的示例,以使其更易于重用——以及轻松地一次允许多个 textView:
class PlaceholderTextView: UITextView, UITextViewDelegate {
private let textLayer = CATextLayer()
public var placeholderText: String = "" {
didSet {
textLayer.string = placeholderText
setNeedsLayout()
}
}
public var placeholderTextColor: UIColor = .lightGray {
didSet {
textLayer.foregroundColor = placeholderTextColor.cgColor
setNeedsLayout()
}
}
override var font: UIFont? {
didSet {
if let fnt = self.font {
textLayer.fontSize = fnt.pointSize
} else {
textLayer.fontSize = 12.0
}
}
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() -> Void {
// textLayer properties
textLayer.contentsScale = UIScreen.main.scale
textLayer.alignmentMode = .left
textLayer.isWrapped = true
textLayer.foregroundColor = placeholderTextColor.cgColor
if let fnt = self.font {
textLayer.fontSize = fnt.pointSize
} else {
textLayer.fontSize = 12.0
}
// insert the textLayer
layer.insertSublayer(textLayer, at: 0)
// set delegate to self
delegate = self
}
override func layoutSubviews() {
super.layoutSubviews()
textLayer.frame = bounds.insetBy(
dx: textContainerInset.left + textContainer.lineFragmentPadding,
dy: textContainerInset.top
)
}
func textViewDidChange(_ textView: UITextView) {
// show / hide the textLayer
textLayer.opacity = textView.text.isEmpty ? 1.0 : 0.0
}
}
class PlaceHolderTestViewController: UIViewController {
let sampleTextView = PlaceholderTextView()
let placeholderText = "Type Something"
let placeholderTextColor = UIColor.lightGray
let normalTextColor = UIColor.label
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray
if self.navigationController != nil {
// add a "Done" navigation bar button
let btn = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTap))
self.navigationItem.rightBarButtonItem = btn
}
view.addSubview(sampleTextView)
sampleTextView.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
sampleTextView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40),
sampleTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 10),
sampleTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -10),
sampleTextView.heightAnchor.constraint(equalToConstant: 100)
])
sampleTextView.font = .systemFont(ofSize: 16.0)
sampleTextView.textColor = normalTextColor
sampleTextView.placeholderTextColor = placeholderTextColor
sampleTextView.placeholderText = placeholderText
}
@objc func doneTap() -> Void {
view.endEditing(true)
}
}
注意 -- 这只是 示例代码 不应被视为“生产准备好了。
我做了一些研究,发现了这个 post:Add placeholder text inside UITextView in Swift?
将上面的示例合并到我的代码中,我在空白 xcode UIKit 项目中有以下内容:
import UIKit
class ViewController: UIViewController {
var sampleTextView = UITextView()
let placeholderText = "Type Something"
let placeholderTextColor = UIColor.lightGray
let normalTextColor = UIColor.label
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray
sampleTextView.delegate = self
sampleTextView.text = placeholderText
sampleTextView.textColor = placeholderTextColor
sampleTextView.becomeFirstResponder()
sampleTextView.selectedTextRange = sampleTextView.textRange(from: sampleTextView.beginningOfDocument, to: sampleTextView.beginningOfDocument)
view.addSubview(sampleTextView)
sampleTextView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
sampleTextView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0),
sampleTextView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
sampleTextView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10),
sampleTextView.heightAnchor.constraint(equalToConstant: 100)
])
}
}
extension ViewController: UITextViewDelegate {
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = placeholderText
textView.textColor = placeholderTextColor
}
}
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 = placeholderText
textView.textColor = placeholderTextColor
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 == placeholderTextColor && !text.isEmpty {
textView.textColor = normalTextColor
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
}
func textViewDidChangeSelection(_ textView: UITextView) {
if self.view.window != nil {
if textView.textColor == placeholderTextColor {
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
}
}
}
}
但我有两个错误,我无法解决这个问题:
- 这首先是使用键盘工具栏上方的预测文本。重复第一个选定的作品 - 请参见下面的屏幕截图:
- 如果键入,第一个字母按预期按下键盘的 SHIFT 键,但第二个字母也保持按下状态 - 请参见下面的屏幕截图:
抱歉,如果这些是基本的,但我很难过。
情侣笔记...
我认为 Shift 没有被释放,因为你 return False
来自 shouldChangeTextIn range
...所以 textView 没有完全与键盘沟通。
至于重复的预测文本...我 运行 遇到过类似的问题。我的印象是文本 已经插入 并且在调用 shouldChangeTextIn range
时设置了新的 .selectedRange
。所以代码(如所写)复制了它。
由于这些(和其他)复杂性,这里有一个使用 CATextLayer
作为“占位符”文本的示例:
class PlaceHolderTestViewController: UIViewController, UITextViewDelegate {
var sampleTextView = UITextView()
let placeholderText = "Type Something"
let placeholderTextColor = UIColor.lightGray
let normalTextColor = UIColor.label
let textLayer = CATextLayer()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray
if self.navigationController != nil {
// add a "Done" navigation bar button
let btn = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTap))
self.navigationItem.rightBarButtonItem = btn
}
view.addSubview(sampleTextView)
sampleTextView.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
sampleTextView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40),
sampleTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 10),
sampleTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -10),
sampleTextView.heightAnchor.constraint(equalToConstant: 100)
])
sampleTextView.font = .systemFont(ofSize: 16.0)
// textLayer properties
textLayer.contentsScale = UIScreen.main.scale
textLayer.alignmentMode = .left
textLayer.isWrapped = true
textLayer.foregroundColor = placeholderTextColor.cgColor
textLayer.string = placeholderText
if let fnt = sampleTextView.font {
textLayer.fontSize = fnt.pointSize
} else {
textLayer.fontSize = 12.0
}
// insert the textLayer
sampleTextView.layer.insertSublayer(textLayer, at: 0)
// set delegate to self
sampleTextView.delegate = self
}
func textViewDidChange(_ textView: UITextView) {
textLayer.opacity = textView.text.isEmpty ? 1.0 : 0.0
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// update textLayer frame here
textLayer.frame = sampleTextView.bounds.insetBy(
dx: sampleTextView.textContainerInset.left + sampleTextView.textContainer.lineFragmentPadding,
dy: sampleTextView.textContainerInset.top
)
}
@objc func doneTap() -> Void {
view.endEditing(true)
}
}
并且,这里有一个子类化 UITextView
的示例,以使其更易于重用——以及轻松地一次允许多个 textView:
class PlaceholderTextView: UITextView, UITextViewDelegate {
private let textLayer = CATextLayer()
public var placeholderText: String = "" {
didSet {
textLayer.string = placeholderText
setNeedsLayout()
}
}
public var placeholderTextColor: UIColor = .lightGray {
didSet {
textLayer.foregroundColor = placeholderTextColor.cgColor
setNeedsLayout()
}
}
override var font: UIFont? {
didSet {
if let fnt = self.font {
textLayer.fontSize = fnt.pointSize
} else {
textLayer.fontSize = 12.0
}
}
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() -> Void {
// textLayer properties
textLayer.contentsScale = UIScreen.main.scale
textLayer.alignmentMode = .left
textLayer.isWrapped = true
textLayer.foregroundColor = placeholderTextColor.cgColor
if let fnt = self.font {
textLayer.fontSize = fnt.pointSize
} else {
textLayer.fontSize = 12.0
}
// insert the textLayer
layer.insertSublayer(textLayer, at: 0)
// set delegate to self
delegate = self
}
override func layoutSubviews() {
super.layoutSubviews()
textLayer.frame = bounds.insetBy(
dx: textContainerInset.left + textContainer.lineFragmentPadding,
dy: textContainerInset.top
)
}
func textViewDidChange(_ textView: UITextView) {
// show / hide the textLayer
textLayer.opacity = textView.text.isEmpty ? 1.0 : 0.0
}
}
class PlaceHolderTestViewController: UIViewController {
let sampleTextView = PlaceholderTextView()
let placeholderText = "Type Something"
let placeholderTextColor = UIColor.lightGray
let normalTextColor = UIColor.label
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray
if self.navigationController != nil {
// add a "Done" navigation bar button
let btn = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTap))
self.navigationItem.rightBarButtonItem = btn
}
view.addSubview(sampleTextView)
sampleTextView.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
sampleTextView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40),
sampleTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 10),
sampleTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -10),
sampleTextView.heightAnchor.constraint(equalToConstant: 100)
])
sampleTextView.font = .systemFont(ofSize: 16.0)
sampleTextView.textColor = normalTextColor
sampleTextView.placeholderTextColor = placeholderTextColor
sampleTextView.placeholderText = placeholderText
}
@objc func doneTap() -> Void {
view.endEditing(true)
}
}
注意 -- 这只是 示例代码 不应被视为“生产准备好了。