英文:
How to position the cursor at the beginning of the placeholder text in UITextview
问题
I spent a lot of time trying to fix this simple issues but don't managed it. I want in UITextView to use a placeholder: "translation" and when tapping the UITextView the placeholder should stay there until a character is entered but I need the cursor to be placed at the beginning of the placeholder just before "translation" word but it stays at the end of my placeholder. I find several answers and tried to do it as following but doesn't work. The placeholder changes correctly to black color but the 2nd command -> textView.selectedRange = NSMakeRange(0, 0) is not moving the cursor to the beginning, why?
- I replaced this NSMakeRange line with following (other suggestion I read but same result, doesn't work) -> textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
Here the full code:
class MainViewController: UIViewController, UITextViewDelegate, UITextFieldDelegate {
@IBOutlet weak var definitionField: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// setting placeholder
definitionField.textColor = .gray
definitionField.text = "translation"
definitionField.delegate = self
}
func textViewDidBeginEditing(_ textView: UITextView) {
if (textView.text == "translation") {
textView.textColor = .black
textView.selectedRange = NSMakeRange(0, 0)
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if (textView.text == "") {
textView.text = "translation"
textView.textColor = .gray
}
textView.resignFirstResponder()
}
}
英文:
I spent a lot of time trying to fix this simple issues but don't managed it. I want in UITextView to use a placeholder: "translation" and when tapping the UITextView the placeholder should stay there until a character is entered but I need the cursor to be placed at the beginning of the placeholder just before "translation" word but it stays at the end of my placeholder. I find several answers and tried to do it as following but doesn't work. The placeholder changes correctly to black colour but the 2nd command -> textView.selectedRange = NSMakeRange(0, 0) is not moving the cursor to the beginning, why?
- I replaced this NSMakeRange line with following (other suggestion I read but same result, doesn't work) -> textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
Here the full code:
class MainViewController: UIViewController, UITextViewDelegate, UITextFieldDelegate {
@IBOutlet weak var definitionField: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// setting placeholder
definitionField.textColor = .gray
definitionField.text = "translation"
definitionField.delegate = self }
func textViewDidBeginEditing(_ textView: UITextView) {
if (textView.text == "translation") {
textView.textColor = .black
textView.selectedRange = NSMakeRange(0, 0)
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if (textView.text == "") {
textView.text = "translation"
textView.textColor = .gray
}
textView.resignFirstResponder()
}
答案1
得分: 1
以下是您要翻译的内容:
"selectedTextRange" 在 "textViewDidBeginEditing" 被调用之后,系统会自动设置,并且在调用 "becomeFirstResponder" 后也会设置。您可以通过使用自定义的 "UITextView" 子类并覆盖以下代码来观察:
override var selectedTextRange: UITextRange? {
didSet {
print(selectedTextRange)
}
}
您将看到,由于您的代码,它首先被设置为 "(0, 0)",然后 "didSet" 会被“神奇地”再次调用,将 "selectedTextRange" 设置为 "(11, 0)"。
我不确定是哪个方法进行了调用。甚至可能没有一个可重写的方法在此之后被调用。
一个简单的解决方案是只需使用 "DispatchQueue.main.async" 来等待设置所选文本范围的任何操作完成,然后再更改选择范围。
// 在 textViewDidBeginEditing 中
DispatchQueue.main.async {
textView.selectedRange = NSMakeRange(0, 0)
}
光标会在最后出现一小段时间,然后回到开头。这似乎只是视觉效果。当我尝试在光标位于末尾时输入文本时,文本会在“翻译”之前插入。
话虽如此,您对这个占位符的实现似乎与人们期望的占位符工作方式有很大不同(就像 "UITextField" 中的占位符),尤其是在 "textViewDidEndEditing" 时。
如果您希望实现类似于 "UITextField" 的行为,或者想要查看其他方法,请参阅 此帖子。
英文:
It appears that selectedTextRange
is being set automatically by the system, after textViewDidBeginEditing
, and becomeFirstResponder
is called. You can observe this by using a custom UITextView
subclass, and overriding:
override var selectedTextRange: UITextRange? {
didSet {
print(selectedTextRange)
}
}
You will see that this first gets set to (0, 0)
because of your code, and then didSet
is "magically" called again, setting selectedTextRange
to (11, 0)
.
I'm not sure which method did the call. There might not even be a overridable method that gets called after that.
A simple solution would be to just use DispatchQueue.main.async
to wait for whatever is setting the selected text range to the end, and then change the selection.
// in textViewDidBeginEditing
DispatchQueue.main.async {
textView.selectedRange = NSMakeRange(0, 0)
}
The cursor will appear at the end for a brief moment, before going to the start. This seems to be only visual. When I tried to enter text when the cursor is at the end, the text is inserted before "translation".
That said, your implementation of this placeholder seems to have rather different from how one would expect a placeholder to work (like the one in UITextField
), especially when textViewDidEndEditing
.
If you want behaviours similar to UITextField
, or if you want to see other approaches, check out this post.
答案2
得分: 0
以下是翻译好的内容:
最简单的解决方法是添加一个包含占位符的标签,然后根据文本视图中是否有任何文本来设置其 isHidden
属性。
这里有一个可用示例,但它使用 RxSwift 来跟踪文本、颜色和字体的更改。
它的功能包括:
- 创建一个标签并将其嵌入到文本视图的适当位置。
- 监视文本视图的 tintColor 并更新标签的 textColor。
- 监视文本视图的字体并更新标签的字体。
- 监视文本视图的文本并根据是否有文本来更新标签的 isHidden 属性。
extension UITextView {
func withPlaceholder(_ placeholder: String) {
let label = {
let result = UILabel(frame: bounds)
result.text = placeholder
result.numberOfLines = 0
result.frame = result.frame.offsetBy(dx: 4, dy: 8)
return result
}()
addSubview(label)
_ = rx.observe(UIColor.self, "tintColor")
.take(until: rx.deallocating)
.bind(to: label.rx.textColor)
_ = rx.observe(UIFont.self, "font")
.map { $0 != nil ? $0 : UIFont.systemFont(ofSize: 12) }
.take(until: rx.deallocating)
.bind(onNext: { [weak self] font in
label.font = font
label.frame.size = label.sizeThatFits(self?.bounds.size ?? CGSize.zero)
})
_ = rx.text.orEmpty.map { !$0.isEmpty }
.take(until: rx.deallocating)
.bind(to: label.rx.isHidden)
}
}
英文:
The simplest solution here is to add a label that holds the placeholder. Then set its isHidden
property based on whether there is any text in the text view.
Here is a working example but it uses RxSwift to track text, color, and font changes.
What it does:
- create a label and embed it into the text view at the proper spot.
- monitor the tintColor of the text view and update the textColor of the label.
- monitor the font of the text view and update the font of the label.
- monitor the text of the text view and update the isHidden property of the label based on whether there is any text.
extension UITextView {
func withPlaceholder(_ placeholder: String) {
let label = {
let result = UILabel(frame: bounds)
result.text = placeholder
result.numberOfLines = 0
result.frame = result.frame.offsetBy(dx: 4, dy: 8)
return result
}()
addSubview(label)
_ = rx.observe(UIColor.self, "tintColor")
.take(until: rx.deallocating)
.bind(to: label.rx.textColor)
_ = rx.observe(UIFont.self, "font")
.map { $0 != nil ? $0 : UIFont.systemFont(ofSize: 12) }
.take(until: rx.deallocating)
.bind(onNext: { [weak self] font in
label.font = font
label.frame.size = label.sizeThatFits(self?.bounds.size ?? CGSize.zero)
})
_ = rx.text.orEmpty.map { !$0.isEmpty }
.take(until: rx.deallocating)
.bind(to: label.rx.isHidden)
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论