如何通过点击 UITextView 中的特定字符来显示圆角灰色背景?
Posted
技术标签:
【中文标题】如何通过点击 UITextView 中的特定字符来显示圆角灰色背景?【英文标题】:How to display rounded gray background by tapping specific characters in UITextView? 【发布时间】:2018-02-20 02:47:13 【问题描述】:我正在尝试使用 UITextView 显示文本。我在显示长文本时添加了“查看更多”。我想在点击它时更改背景颜色。我设置了NSAttributedString的背景,但是不能很好的设置圆角和边距。
谢谢!
我想做的事!
点击添加到 UITextView 的字符时,灰色背景具有足够的边距和圆角。
注意:已经可以点击一个字符。这个问题是关于敲击时的效果。
类似问题
NSAttributedString background color and rounded corners
How to set NSString's background cornerRadius on ios7
【问题讨论】:
点击“查看更多”后,您可以看到全文。需要用背景颜色显示剩余的文本吗? @McDonal_11 这是识别水龙头的效果。也就是说,当您点击 (touchesBegan) 时,它会变成灰色背景,而当您松开它时,它会消失 (touchesEnded)。 “查看更多”就是一个例子。这个问题是关于点击 UITextView characters.m 时的效果如何使用 TextKit 来实现的? 您是否使用 TapGesture 进行 TextView ?或者,你觉得怎么样? @McDonal_11 感谢您的评论。我使用 hittest 来识别水龙头。我可以获得点击位置并确定目标字符是否被点击。我不能做的是在点击其目标字符时添加灰色背景(圆角,带边距的大小)的效果。附加图像是 Facebook iOS 应用程序上的“查看更多”和“链接”点击时的屏幕截图。一个即使发生换行也正确设置灰色背景的示例。 我已经越过了灰色级别。尝试圆角。 【参考方案1】:在 UITextView 的文本中添加圆角背景颜色。这个answer
将为您的Question
提供一些想法。
逻辑:
在UITextView
中,我添加了UITapGestureRecognizer
,它通过Character
检测用户的点击动作Character
。如果用户点击subString
中的任何一个字符,将创建新的UIView
并触发计时器。当计时器结束时,创建的 UIView 将从 UITextView 中删除。
借助myTextView.position
,我们可以得到子字符串的CGRect
。那是Created UIView
的框架。 Size (WIDTH)
对于subString
中的每个单词,可以从SizeAtrributes
获取。
@IBOutlet weak var challengeTextVw: UITextView!
let fullText = "We Love Swift and Swift attributed text "
var myString = NSMutableAttributedString ()
let subString = " Swift attributed text "
var subStringSizesArr = [CGFloat]()
var myRange = NSRange()
var myWholeRange = NSRange()
let fontSize : CGFloat = 25
var timerTxt = Timer()
let delay = 3.0
override func viewDidLoad()
super.viewDidLoad()
myString = NSMutableAttributedString(string: fullText)
myRange = (fullText as! NSString).range(of: subString)
myWholeRange = (fullText as! NSString).range(of: fullText)
let substringSeperatorArr = subString.components(separatedBy: " ")
print(substringSeperatorArr)
print(substringSeperatorArr.count)
var strConcat = " "
for str in 0..<substringSeperatorArr.count
strConcat = strConcat + substringSeperatorArr[str] + " "
let textSize = (strConcat as! NSString).size(withAttributes: [NSAttributedStringKey.font : UIFont.systemFont(ofSize: fontSize)])
print("strConcatstrConcat ", strConcat)
if str != 0 && str != (substringSeperatorArr.count - 2)
print("times")
subStringSizesArr.append(textSize.width)
let myCustomAttribute = [NSAttributedStringKey.init("MyCustomAttributeName") : "some value", NSAttributedStringKey.foregroundColor : UIColor.orange] as [NSAttributedStringKey : Any]
let fontAtrib = [NSAttributedStringKey.font : UIFont.systemFont(ofSize: fontSize)]
myString.addAttributes(myCustomAttribute, range: myRange)
myString.addAttributes(fontAtrib, range: myWholeRange)
challengeTextVw.attributedText = myString
let tap = UITapGestureRecognizer(target: self, action: #selector(myMethodToHandleTap))
tap.delegate = self
challengeTextVw.addGestureRecognizer(tap)
challengeTextVw.isEditable = false
challengeTextVw.isSelectable = false
@objc func myMethodToHandleTap(_ sender: UITapGestureRecognizer)
let myTextView = sender.view as! UITextView
let layoutManager = myTextView.layoutManager
let numberOfGlyphs = layoutManager.numberOfGlyphs
var numberOfLines = 0
var index = 0
var lineRange:NSRange = NSRange()
while (index < numberOfGlyphs)
layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
index = NSMaxRange(lineRange);
numberOfLines = numberOfLines + 1
print("noLin ", numberOfLines)
// location of tap in myTextView coordinates and taking the inset into account
var location = sender.location(in: myTextView)
location.x -= myTextView.textContainerInset.left;
location.y -= myTextView.textContainerInset.top;
// character index at tap location
let characterIndex = layoutManager.characterIndex(for: location, in: myTextView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
// if index is valid then do something.
if characterIndex < myTextView.textStorage.length
// print the character index
print("character index: \(characterIndex)")
// print the character at the index
let myRangee = NSRange(location: characterIndex, length: 1)
let substring = (myTextView.attributedText.string as NSString).substring(with: myRangee)
print("character at index: \(substring)")
// check if the tap location has a certain attribute
let attributeName = NSAttributedStringKey.init("MyCustomAttributeName")
let attributeValue = myTextView.attributedText.attribute(attributeName, at: characterIndex, effectiveRange: nil) as? String
if let value = attributeValue
print("You tapped on \(attributeName) and the value is: \(value)")
print("\n\n ererereerer")
timerTxt = Timer.scheduledTimer(timeInterval: delay, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false)
myTextView.layoutManager.ensureLayout(for: myTextView.textContainer)
// text position of the range.location
let start = myTextView.position(from: myTextView.beginningOfDocument, offset: myRange.location)!
// text position of the end of the range
let end = myTextView.position(from: start, offset: myRange.length)!
// text range of the range
let tRange = myTextView.textRange(from: start, to: end)
// here it is!
let rect = myTextView.firstRect(for: tRange!) //firstRectForRange(tRange)
var secondViewWidthIndex = Int()
for count in 0..<subStringSizesArr.count
if rect.width > subStringSizesArr[count]
secondViewWidthIndex = count
let backHideVw = UIView()
backHideVw.frame.origin.x = rect.origin.x
backHideVw.frame.origin.y = rect.origin.y + 1
backHideVw.frame.size.height = rect.height
backHideVw.frame.size.width = rect.width
backHideVw.backgroundColor = UIColor.brown
backHideVw.layer.cornerRadius = 2
backHideVw.tag = 10
myTextView.addSubview(backHideVw)
myTextView.sendSubview(toBack: backHideVw)
if numberOfLines > 1
let secondView = UIView()
secondView.frame.origin.x = 0
secondView.frame.origin.y = backHideVw.frame.origin.y + backHideVw.frame.size.height
secondView.frame.size.height = backHideVw.frame.size.height
secondView.frame.size.width = (subStringSizesArr.last! - subStringSizesArr[secondViewWidthIndex]) + 2
secondView.backgroundColor = UIColor.brown
secondView.layer.cornerRadius = 2
secondView.tag = 20
backHideVw.frame.size.width = subStringSizesArr[secondViewWidthIndex]
myTextView.addSubview(secondView)
print("secondView.framesecondView.frame ", secondView.frame)
myTextView.sendSubview(toBack: secondView)
print("rectrect ", rect)
@objc func delayedAction()
for subVws in challengeTextVw.subviews
if (String(describing: subVws).range(of:"UIView") != nil)
if (subVws as! UIView).tag == 10 || (subVws as! UIView).tag == 20
subVws.removeFromSuperview()
通过增加Font Size
,尝试了所有尝试。
尝试 1
尝试 2
尝试 3
【讨论】:
由于我一直显示背景直到我释放它,我将通过触摸动作而不是计时器来控制它。使用 UIView 是一种解决方案。作为替代解决方案,我试图通过使用 NSLayoutManager 的子类来实现这一点。这个想法来自this question。这个怎么样?我还提出了一个与this 相关的新问题。【参考方案2】:如果问题是关于获取点击字符的范围,你应该使用layoutManager.characterIndex(for:in:fractionOfDistanceBetweenInsertionPoints:)
:
private var selectGestureRecognizer: UITapGestureRecognizer!
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
selectGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(textTapped))
addGestureRecognizer(selectGestureRecognizer)
@objc func textTapped(recognizer: UITapGestureRecognizer)
guard recognizer == selectGestureRecognizer else
return
var location = recognizer.location(in: self)
// https://***.com/a/25466154/1033581
location.x -= textContainerInset.left
location.y -= textContainerInset.top
let charIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
let range = NSRange(location: charIndex, length: 1)
// do something with the tapped range
您还可以根据需要使用不同的 UIGestureRecognizer。
如果您的问题是关于如何为一系列文本绘制自定义背景,您可以使用layoutManager.boundingRect(forGlyphRange:in:)
:
func layoutBackground(range: NSRange) -> CALayer
let rect = boundingRectForCharacterRange(range)
let backgroundLayer = CALayer()
backgroundLayer.frame = rect
backgroundLayer.cornerRadius = 5
backgroundLayer.backgroundColor = UIColor.black.cgColor.copy(alpha: 0.2)
layer.insertSublayer(backgroundLayer, at: 0)
return backgroundLayer
/// CGRect of substring.
func boundingRectForCharacterRange(_ range: NSRange) -> CGRect
let layoutManager = self.layoutManager
var glyphRange = NSRange()
// Convert the range for glyphs.
layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange)
var glyphRect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
// https://***.com/a/28332722/1033581
glyphRect.origin.x += textContainerInset.left
glyphRect.origin.y += textContainerInset.top
return glyphRect
注意事项:
此外,您应该保留对以这种方式创建的图层的引用,以便您可以删除或重新构建它们。 当范围跨越多行时,layoutManager.boundingRect(forGlyphRange:in:)
可能无法为您提供所需的内容,因此请考虑在适当的时候拆分范围。
【讨论】:
以上是关于如何通过点击 UITextView 中的特定字符来显示圆角灰色背景?的主要内容,如果未能解决你的问题,请参考以下文章
Swift 4 或 Swift 5 中的 UITextView 用户输入文本验证:限制 Swift 中的特定字符
选择 UITableView 行时使用特定信息填充 UITextView