在 UITextView 中放置附加文本 - WhatsApp 时间戳副本
Position additional text inside UITextView - WhatsApp timestamp copy
看下图的Whatsapp,尤其是红圈内的文字:
图片上有 Lorum 文本和消息时间戳的时间戳。我想知道 Whatsapp 是如何做到这一点的。
我认为 Whatsapp 使用的是 UITextView
,但我不确定。我想知道如何制作这种电池。我尝试使用 attributedText 属性 添加时间戳,但我在计算正确的大小时遇到了很多麻烦。也许有一个简单的解决方案。
注意:没有xibs/storyboards,只是代码。
注 2:如图所示,UITextView 中的文本环绕在时间戳周围 。这是我想要复制的行为。
您可以制作一个 .xib 文件并将文本放入 textView,将时间放入标签,对于复选框使用 ImageView。像这样:
为了实现 WhatsApp 聊天行的布局,即消息 + 时间,我使用了以下策略。
使用 2 个标签。第一个用于消息和第二个日期。第一次,我对顶部、底部、左侧和右侧的视图进行了限制。第二次我将它限制在视图的右侧和底部。
现在为了避免第 1 条和第 2 条重叠,我在聊天消息末尾添加了 "blank spaces"。例如,如果消息是 "Hi"
,我发送到 "Hi "
。这确保我的时间和滴答不重叠。
该任务的主要问题是告诉 TextView 避免在时间戳上方键入文本。
您可以使用 textView.textContainer.exclusionPaths
轻松完成此操作:
CGFloat width = [UIScreen mainScreen].bounds.size.width - self.textViewLeading.constant - self.textViewTrailing.constant;
UIBezierPath * exclusionPath = [UIBezierPath bezierPathWithRect:CGRectMake(width - self.timestampContainerWidth.constant, 0, self.timestampContainerWidth.constant, self.timestampContainerHeight.constant)];
self.textView.textContainer.exclusionPaths = @[exclusionPath];
就是这样。当然,你应该在右下角添加时间戳容器视图。
文档:https://developer.apple.com/documentation/uikit/nstextcontainer/1444569-exclusionpaths
我创建了一些虚拟测试项目,我想它可以回答您的问题。
我把 textView
作为文本占位符并添加了一些 imageView
。没有什么特别的约束。可以看到一张图
然后所有魔法都发生在 tableView 委托方法中:cellForRowAt indexPath:
,我使用 textView
中的 UIBezierPath
排除了 imageView
帧。因此,在您的情况下,您必须将 UIBezierPath
的 rect
设置为右下角。
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let contentOfCell = data[indexPath.row]
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? CustomCell else {
return UITableViewCell()
}
cell.descriptionTextView.text = contentOfCell
let textContainerPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: cell.CellImageView.frame.width, height: cell.CellImageView.frame.height))
cell.descriptionTextView.textContainer.exclusionPaths = [textContainerPath]
return cell
}
最终结果如下所示:
我尝试了各种选项,发现手动计算可以很好地满足您的要求,而不是使用排除路径或 NSAttributedString
。
这是我能够在不使用任何 Xib/storyboard 的情况下使 WhatsApp 像聊天视图一样的屏幕截图
这是代码,需要的地方添加注释:
func createChatView() {
let msgViewMaxWidth = UIScreen.main.bounds.width * 0.7 // 70% of screen width
let message = "Filler text is text that shares some characteristics of a real written text, but is random or otherwise generated. It may be used to display a sample of fonts, generate text for testing, or to spoof an e-mail spam filter."
// Main container view
let messageView = UIView(frame: CGRect(x: UIScreen.main.bounds.width * 0.1, y: 150, width: msgViewMaxWidth, height: 0))
messageView.backgroundColor = UIColor(red: 0.803, green: 0.99, blue: 0.780, alpha: 1)
messageView.clipsToBounds = true
messageView.layer.cornerRadius = 5
let readStatusImg = UIImageView()
readStatusImg.image = UIImage(named: "double-tick-indicator.png")
readStatusImg.frame.size = CGSize(width: 12, height: 12)
let timeLabel = UILabel(frame: CGRect(x: 0, y: 0, width: messageView.bounds.width, height: 0))
timeLabel.font = UIFont.systemFont(ofSize: 10)
timeLabel.text = "12:12 AM"
timeLabel.sizeToFit()
timeLabel.textColor = .gray
let textView = UITextView(frame: CGRect(x: 0, y: 0, width: messageView.bounds.width, height: 0))
textView.isEditable = false
textView.isScrollEnabled = false
textView.showsVerticalScrollIndicator = false
textView.showsHorizontalScrollIndicator = false
textView.backgroundColor = .clear
textView.text = message
// Wrap time label and status image in single view
// Here stackview can be used if ios 9 below are not support by your app.
let rightBottomView = UIView()
let rightBottomViewHeight: CGFloat = 16
// Here 7 pts is used to keep distance between timestamp and status image
// and 5 pts is used for trail space.
rightBottomView.frame.size = CGSize(width: readStatusImg.frame.width + 7 + timeLabel.frame.width + 5, height: rightBottomViewHeight)
rightBottomView.addSubview(timeLabel)
readStatusImg.frame.origin = CGPoint(x: timeLabel.frame.width + 7, y: 0)
rightBottomView.addSubview(readStatusImg)
// Fix right and bottom margin
rightBottomView.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin]
messageView.addSubview(textView)
messageView.addSubview(rightBottomView)
// Update textview height
textView.sizeToFit()
// Update message view size with textview size
messageView.frame.size = textView.frame.size
// Keep at right bottom in parent view
rightBottomView.frame.origin = CGPoint(x: messageView.bounds.width - rightBottomView.bounds.width, y: messageView.bounds.height - rightBottomView.bounds.height)
// Get glyph index in textview, make sure there is atleast one character present in message
let lastGlyphIndex = textView.layoutManager.glyphIndexForCharacter(at: message.count - 1)
// Get CGRect for last character
let lastLineFragmentRect = textView.layoutManager.lineFragmentUsedRect(forGlyphAt: lastGlyphIndex, effectiveRange: nil)
// Check whether enough space is avaiable to show in last line of message, if not add extra height for timestamp
if lastLineFragmentRect.maxX > (textView.frame.width - rightBottomView.frame.width) {
// Subtracting 5 to reduce little top spacing for timestamp
messageView.frame.size.height += (rightBottomViewHeight - 5)
}
self.view.addSubview(messageView)
}
希望这能解决您的问题。
我在使用其他方法创建聊天视图时遇到的挑战是:
UITextView exclusionPaths - 它并非在所有条件下都有效,我也注意到问题,例如有时它会在排除路径周围提供更多额外的 space然后需要。当文本完全占据 UITextView space 时,它也会失败,在这种情况下,时间戳应该转到新行,但这不会发生。
NSAttributedString - 实际上,我无法使用 NSAttributedString
创建此聊天视图,但在尝试时我发现它让我写了一个很多代码加上很难 manage/update.
看下图的Whatsapp,尤其是红圈内的文字:
图片上有 Lorum 文本和消息时间戳的时间戳。我想知道 Whatsapp 是如何做到这一点的。
我认为 Whatsapp 使用的是 UITextView
,但我不确定。我想知道如何制作这种电池。我尝试使用 attributedText 属性 添加时间戳,但我在计算正确的大小时遇到了很多麻烦。也许有一个简单的解决方案。
注意:没有xibs/storyboards,只是代码。
注 2:如图所示,UITextView 中的文本环绕在时间戳周围 。这是我想要复制的行为。
您可以制作一个 .xib 文件并将文本放入 textView,将时间放入标签,对于复选框使用 ImageView。像这样:
为了实现 WhatsApp 聊天行的布局,即消息 + 时间,我使用了以下策略。 使用 2 个标签。第一个用于消息和第二个日期。第一次,我对顶部、底部、左侧和右侧的视图进行了限制。第二次我将它限制在视图的右侧和底部。
现在为了避免第 1 条和第 2 条重叠,我在聊天消息末尾添加了 "blank spaces"。例如,如果消息是 "Hi"
,我发送到 "Hi "
。这确保我的时间和滴答不重叠。
该任务的主要问题是告诉 TextView 避免在时间戳上方键入文本。
您可以使用 textView.textContainer.exclusionPaths
轻松完成此操作:
CGFloat width = [UIScreen mainScreen].bounds.size.width - self.textViewLeading.constant - self.textViewTrailing.constant;
UIBezierPath * exclusionPath = [UIBezierPath bezierPathWithRect:CGRectMake(width - self.timestampContainerWidth.constant, 0, self.timestampContainerWidth.constant, self.timestampContainerHeight.constant)];
self.textView.textContainer.exclusionPaths = @[exclusionPath];
就是这样。当然,你应该在右下角添加时间戳容器视图。
文档:https://developer.apple.com/documentation/uikit/nstextcontainer/1444569-exclusionpaths
我创建了一些虚拟测试项目,我想它可以回答您的问题。
我把 textView
作为文本占位符并添加了一些 imageView
。没有什么特别的约束。可以看到一张图
然后所有魔法都发生在 tableView 委托方法中:cellForRowAt indexPath:
,我使用 textView
中的 UIBezierPath
排除了 imageView
帧。因此,在您的情况下,您必须将 UIBezierPath
的 rect
设置为右下角。
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let contentOfCell = data[indexPath.row]
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? CustomCell else {
return UITableViewCell()
}
cell.descriptionTextView.text = contentOfCell
let textContainerPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: cell.CellImageView.frame.width, height: cell.CellImageView.frame.height))
cell.descriptionTextView.textContainer.exclusionPaths = [textContainerPath]
return cell
}
最终结果如下所示:
我尝试了各种选项,发现手动计算可以很好地满足您的要求,而不是使用排除路径或 NSAttributedString
。
这是我能够在不使用任何 Xib/storyboard 的情况下使 WhatsApp 像聊天视图一样的屏幕截图
这是代码,需要的地方添加注释:
func createChatView() {
let msgViewMaxWidth = UIScreen.main.bounds.width * 0.7 // 70% of screen width
let message = "Filler text is text that shares some characteristics of a real written text, but is random or otherwise generated. It may be used to display a sample of fonts, generate text for testing, or to spoof an e-mail spam filter."
// Main container view
let messageView = UIView(frame: CGRect(x: UIScreen.main.bounds.width * 0.1, y: 150, width: msgViewMaxWidth, height: 0))
messageView.backgroundColor = UIColor(red: 0.803, green: 0.99, blue: 0.780, alpha: 1)
messageView.clipsToBounds = true
messageView.layer.cornerRadius = 5
let readStatusImg = UIImageView()
readStatusImg.image = UIImage(named: "double-tick-indicator.png")
readStatusImg.frame.size = CGSize(width: 12, height: 12)
let timeLabel = UILabel(frame: CGRect(x: 0, y: 0, width: messageView.bounds.width, height: 0))
timeLabel.font = UIFont.systemFont(ofSize: 10)
timeLabel.text = "12:12 AM"
timeLabel.sizeToFit()
timeLabel.textColor = .gray
let textView = UITextView(frame: CGRect(x: 0, y: 0, width: messageView.bounds.width, height: 0))
textView.isEditable = false
textView.isScrollEnabled = false
textView.showsVerticalScrollIndicator = false
textView.showsHorizontalScrollIndicator = false
textView.backgroundColor = .clear
textView.text = message
// Wrap time label and status image in single view
// Here stackview can be used if ios 9 below are not support by your app.
let rightBottomView = UIView()
let rightBottomViewHeight: CGFloat = 16
// Here 7 pts is used to keep distance between timestamp and status image
// and 5 pts is used for trail space.
rightBottomView.frame.size = CGSize(width: readStatusImg.frame.width + 7 + timeLabel.frame.width + 5, height: rightBottomViewHeight)
rightBottomView.addSubview(timeLabel)
readStatusImg.frame.origin = CGPoint(x: timeLabel.frame.width + 7, y: 0)
rightBottomView.addSubview(readStatusImg)
// Fix right and bottom margin
rightBottomView.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin]
messageView.addSubview(textView)
messageView.addSubview(rightBottomView)
// Update textview height
textView.sizeToFit()
// Update message view size with textview size
messageView.frame.size = textView.frame.size
// Keep at right bottom in parent view
rightBottomView.frame.origin = CGPoint(x: messageView.bounds.width - rightBottomView.bounds.width, y: messageView.bounds.height - rightBottomView.bounds.height)
// Get glyph index in textview, make sure there is atleast one character present in message
let lastGlyphIndex = textView.layoutManager.glyphIndexForCharacter(at: message.count - 1)
// Get CGRect for last character
let lastLineFragmentRect = textView.layoutManager.lineFragmentUsedRect(forGlyphAt: lastGlyphIndex, effectiveRange: nil)
// Check whether enough space is avaiable to show in last line of message, if not add extra height for timestamp
if lastLineFragmentRect.maxX > (textView.frame.width - rightBottomView.frame.width) {
// Subtracting 5 to reduce little top spacing for timestamp
messageView.frame.size.height += (rightBottomViewHeight - 5)
}
self.view.addSubview(messageView)
}
希望这能解决您的问题。
我在使用其他方法创建聊天视图时遇到的挑战是:
UITextView exclusionPaths - 它并非在所有条件下都有效,我也注意到问题,例如有时它会在排除路径周围提供更多额外的 space然后需要。当文本完全占据 UITextView space 时,它也会失败,在这种情况下,时间戳应该转到新行,但这不会发生。
NSAttributedString - 实际上,我无法使用
NSAttributedString
创建此聊天视图,但在尝试时我发现它让我写了一个很多代码加上很难 manage/update.