在 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 帧。因此,在您的情况下,您必须将 UIBezierPathrect 设置为右下角。

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.