Поместите дополнительный текст внутри UITextView - копия временного штампа WhatsApp - PullRequest
4 голосов
/ 12 мая 2019

См. Следующее изображение WhatsApp, и особенно текст, который находится внутри красного круга:

enter image description here

На изображении есть текст Lorum ивременная метка временной метки сообщения.Мне интересно, как Whatsapp удалось сделать это.

Я думаю, что WhatsApp использует UITextView, но я не уверен в этом.Мне интересно, как я могу сделать такую ​​клетку.Я попытался добавить метку времени с помощью свойства attributetedText, но у меня возникли большие проблемы с вычислением правильных размеров для этого.Возможно, есть простое решение.

Примечание. Никаких xibs / раскадровок, только код.

Примечание 2: как видно на рисунке, текст внутри UITextView обернут вокруготметка времени .Это поведение, которое я хочу повторить.

Ответы [ 5 ]

3 голосов
/ 19 мая 2019

Я испробовал различные варианты и обнаружил, что расчет вручную очень хорошо соответствует вашим требованиям вместо использования пути исключения или NSAttributedString.

Вот снимок экрана, на котором я смог сделать WhatsApp как чат без использования Xib / раскадровки enter image description here

и вот код и, где нужно, добавленные комментарии:

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 - Он работает не во всех условиях, я также заметил проблемы, например, иногда он дает больше дополнительного пространства вокруг пути исключения, чем необходимо. Также происходит сбой, когда текст точно занимает пространство UITextView, в этом случае отметка времени должна переходить на новую строку, но этого не происходит.

  • NSAttributedString - На самом деле, я не смог сделать этот просмотр чата с помощью NSAttributedString, но при попытке обнаружить, что он заставил меня написать много кода, и им было очень трудно управлять /update.

1 голос
/ 19 мая 2019

Я создал какой-то фиктивный тестовый проект, на который я полагаю ответы на ваш вопрос.

enter image description here

Я поместил textView в качестве заполнителя текста идобавил немного imageView.Ничего особенного с ограничениями.Вы можете видеть картинку.

Тогда вся магия происходит в методе делегата tableView: cellForRowAt indexPath:, где я исключаю imageView кадр, используя UIBezierPath из textView.Так что в вашем случае вы должны установить rect для UIBezierPath в качестве правого нижнего угла.

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
}

И конечный результат выглядит так:

enter image description here

1 голос
/ 15 мая 2019

Чтобы добиться макета строки чата в WhatsApp, то есть Message + Time, я использовал следующую стратегию.Используя 2 метки.1-й для сообщения и 2-й даты.Для 1-го у меня был ограниченный вид сверху, снизу, слева и справа.Для 2-го я ограничил его справа и снизу от View.

Теперь, чтобы избежать наложения 1-го и 2-го, я добавил «пробелы» в конце сообщения чата.Например, если сообщение было "Hi", я сделал это "Hi ".Это гарантирует, что мое время и тики не перекрываются.

0 голосов
/ 19 мая 2019

Основной проблемой для этой задачи является указание 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

0 голосов
/ 15 мая 2019

Вы можете создать файл .xib и поместить текст в textView, поместить время в метку, а для флажков использовать ImageView. Примерно так:

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...