NSAttributedString: превратите (несколько) упоминаний @username[идентификатор пользователя] в кликабельные ссылки @username

#swift #nsattributedstring

Вопрос:

Я нахожусь в процессе написания кода для отображения упоминаний в an NSAttributedString , которые необходимо связать с профилем пользователя. Формат упоминаний таков @username[userid] , что их нужно было бы отображать как простые @username , которые можно использовать.

У меня есть код, работающий до сих пор, так что имя пользователя становится доступным для кликабельности, но теперь мне нужно удалить [userid] часть, которая, конечно, изменяет длину строки, чтобы диапазоны больше не совпадали и т. Д. Не знаю, как я могу это решить.

 import Foundation
import UIKit

let comment = "Hello @kevin[1], @john and @andrew[2]!"

let wholeRange = NSRange(comment.startIndex..<comment.endIndex, in: comment)
let regex = try NSRegularExpression(pattern: #"(@[w.-@] )[(d )]"#)

let attributedString = NSMutableAttributedString(string: comment)

regex.enumerateMatches(in: comment, options: [], range: wholeRange) { match, _, _ in
  guard let match = match else {
    return
  }

  let userIdRange = Range(match.range(at: 2), in: comment)!
  let userId = comment[userIdRange]

  let usernameRange = match.range(at: 1)
  attributedString.addAttribute(NSAttributedString.Key.link, value: URL(string: "test://profile/(userId)")!, range: usernameRange)
}

print(attributedString)
 

Результат прямо сейчас может быть представлен следующим образом при печати:

 Hello {
}@kevin{
    NSLink = "test://profile/1";
}[1], @john and {
}@andrew{
    NSLink = "test://profile/2";
}[2]!{
}
 

Так @kevin и @andrew есть ссылки, @john их нет (чего и следовало ожидать!), но идентификаторы пользователей все равно видны. Конечно, это проблема, которая была решена раньше, но я не могу найти никаких примеров, не знаю, какие ключевые слова искать. Есть много вопросов об обнаружении имен пользователей/упоминаний в строках и еще больше о создании ссылок NSAttributedString , но это не та проблема , которую я пытаюсь решить.

Как бы я превратил @username[userid] упоминания в кликабельные @username ссылки, чтобы [userid] часть была скрыта?

Ответ №1:

Вам просто нужно получить все соответствующие диапазоны, повторить их в обратном порядке, добавить ссылку на него, а затем заменить весь диапазон именем. Что-то вроде:

 let comment = "Hello @kevin[1], @john and @andrew[2]!"
let attributedString = NSMutableAttributedString(string: comment)
let regex = try NSRegularExpression(pattern: #"(@[w.-@] )[(d )]"#)
var ranges: [(NSRange,NSRange,NSRange)] = []
regex.enumerateMatches(in: comment, range: NSRange(comment.startIndex..., in: comment)) { match, _, _ in
  guard let match = match else {
    return
  }
    ranges.append((match.range(at: 0),
                   match.range(at: 1),
                   match.range(at: 2)))
}

ranges.reversed().forEach {
    let userId = attributedString.attributedSubstring(from: $0.2).string
    let username = attributedString.attributedSubstring(from: $0.1).string
    
    attributedString.addAttribute(.link, value: URL(string: "test://profile/(userId)")!, range: $0.0)
    attributedString.replaceCharacters(in: $0.0, with: username)
}
print(attributedString)
 

Это приведет к печати

Привет {
}@кевин{
NSLink = «тест://профиль/1»;
}, @Джон и {
}@Эндрю{
NSLink = «тест://профиль/2»;
}!{
}

Ответ №2:

Быстро сделано:

 let comment = "Hello @kevin[1], @john and @andrew[2]!"

let attributedString = NSMutableAttributedString(string: comment)

let wholeRange = NSRange(attributedString.string.startIndex..<attributedString.string.endIndex, in: attributedString.string)
let regex = try NSRegularExpression(pattern: #"(@[w.-@] )[(d )]"#)

let matches = regex.matches(in: attributedString.string, options: [], range: wholeRange)

matches.reversed().forEach { aResult in
    let fullMatchRange = Range(aResult.range(at: 0), in: attributedString.string)!      //@kevin[1]
    let replacementRange = Range(aResult.range(at: 1), in: attributedString.string)!    //@kevin
    let userIdRange = Range(aResult.range(at: 2), in: attributedString.string)!         // 1

    let atAuthor = String(attributedString.string[replacementRange])

    attributedString.addAttribute(.link,
                                  value: URL(string: "test://profile/(attributedString.string[userIdRange])")!,
                                  range: NSRange(fullMatchRange, in: attributedString.string))
    attributedString.replaceCharacters(in: NSRange(fullMatchRange, in: attributedString.string),
                                       with: atAuthor)
}

print(attributedString)

 

Выход:

 Hello {
}@kevin{
    NSLink = "test://profile/1";
}, @john and {
}@andrew{
    NSLink = "test://profile/2";
}!{
}
 

Что тут можно посмотреть:

  • Я изменил шаблон, для удобства захвата. Смотрите пример в комментарии в разделе forEach() .
  • Я использовал совпадения в обратном порядке, иначе диапазоны больше не будут точными!
  • Я продолжал играть attributedString.string вместо comment того, чтобы на случай, если это «несинхронно».

Комментарии:

1. Ах, использование спичек в обратном порядке было моментом, когда я пропустил лампочку. Так просто, как только он щелкнет! 😅 Я получал сбои, когда делал их в обычном порядке.

2. О, действительно, это был предыдущий тест в моем коде, и я подумал, что сначала вы хотели @john перейти по ссылке test://profile/john , поэтому я тоже создал дополнительные группы/диапазоны. Но сейчас там должно быть чище.