Swift - 用空格替换字符串中的表情符号

9

我有一个方法可以检测字符串中的网址并返回网址及其出现位置的范围。但是,当字符串中包含表情符号时,一切都不再完美。例如:

"I'm gonna do this callenge as soon as I can swing again \n http://youtu.be/SW_d3fGz1hk"

由于表情符号的存在,从文本中提取的url是http://youtu.be/SW_d3fGz1而不是http://youtu.be/SW_d3fGz1hk。我觉得最简单的解决方法是用空格字符替换字符串中的表情符号(因为我需要某些文本样式中的正确范围)。问题是,使用Swift实现这一点非常困难(很可能是我对Swift String API的能力不足)。
我一直尝试这样做,但似乎无法从Unicode代码点数组创建字符串:
var emojilessStringWithSubstitution: String {
    let emojiRanges = [0x1F601...0x1F64F, 0x2702...0x27B0]
    let emojiSet = Set(emojiRanges.flatten())
    let codePoints: [UnicodeScalar] = self.unicodeScalars.map {
        if emojiSet.contains(Int($0.value)) {
            return UnicodeScalar(32)
        }
        return $0
    }
    return String(codePoints)
}

我这样解决问题的方式是不正确的吗?替换表情符号是最好的解决方案吗?如果是,我该如何做呢?

8个回答

20

Swift 5

不要使用这种硬编码的方式来检测 emojis。在 Swift 5 中,您可以轻松地做到这一点。

let inputText = "Some string  with   emoji "

let textWithoutEmoij = inputText.unicodeScalars
    .filter { !$0.properties.isEmojiPresentation }
    .reduce("") { $0 + String($1) }

print(textWithoutEmoij) // Some string  with   emoji 

如果您的应用程序支持iOS 10.0,则这将无法使用。 - Nirav Jain
3
在iOS 14.0时期,支持10.0的应用程序有哪些?! - Abdelahad Darwish
我认为我们不需要使用 reduce()。如果我只是将 filter() 的结果传递给一个字符串构造函数,它似乎可以工作。 - Graham Lea

9

你可以使用模式匹配(用于表情符号模式)从你的String中过滤掉表情符号字符。

extension String {

    var emojilessStringWithSubstitution: String {
        let emojiPatterns = [UnicodeScalar(0x1F601)...UnicodeScalar(0x1F64F),
                             UnicodeScalar(0x2702)...UnicodeScalar(0x27B0)]
        return self.unicodeScalars
            .filter { ucScalar in !(emojiPatterns.contains{ $0 ~= ucScalar }) }
            .reduce("") { $0 + String($1) }
    }  
}

/* example usage */
let str = "I'm gonna do this callenge as soon as I can swing again \n http://youtu.be/SW_d3fGz1hk"
print(str.emojilessStringWithSubstitution)

/* I'm gonna do this callenge as soon as I can swing again
   http://youtu.be/SW_d3fGz1hk */

请注意,上面的内容仅使用了您提供的表情符号间隔,并不能代表所有表情符号,但该方法是通用的,可以通过将其他表情符号间隔包含到emojiPatterns数组中来迅速扩展。
我再次阅读您的问题后意识到,您希望用空格字符替换表情符号,而不是删除它们(上述过滤解决方案会将其删除)。我们可以通过将上面的.filter操作替换为有条件的返回.map操作来实现这一点,就像您的问题一样。
extension String {

    var emojilessStringWithSubstitution: String {
        let emojiPatterns = [UnicodeScalar(0x1F600)...UnicodeScalar(0x1F64F),
                         UnicodeScalar(0x1F300)...UnicodeScalar(0x1F5FF),
                         UnicodeScalar(0x1F680)...UnicodeScalar(0x1F6FF),
                         UnicodeScalar(0x2600)...UnicodeScalar(0x26FF),
                         UnicodeScalar(0x2700)...UnicodeScalar(0x27BF),
                         UnicodeScalar(0xFE00)...UnicodeScalar(0xFE0F)]

        return self.unicodeScalars
            .map { ucScalar in
                emojiPatterns.contains{ $0 ~= ucScalar } ? UnicodeScalar(32) : ucScalar }
            .reduce("") { $0 + String($1) }
    }
}

在上述内容中,现有的表情符号间隔已经被扩展,根据您对此帖子的评论(列出这些间隔),因此表情符号检查现在可能是全面的。

差不多了!字符串“Svsbsvs shsbsv \nhttp://m.youtube.com/watch?v=H6_zvQQ5P8w” 的结果是:“Svsbsvs shsbsv \nhttp://m.youtube.com/watch?v=H6_zvQQ5P8w”。 - Raphael
搞定了。我漏掉了一些范围: et emojiPatterns = [UnicodeScalar(0x1F600)...UnicodeScalar(0x1F64F), UnicodeScalar(0x1F300)...UnicodeScalar(0x1F5FF), UnicodeScalar(0x1F680)...UnicodeScalar(0x1F6FF), UnicodeScalar(0x2600)...UnicodeScalar(0x26FF), UnicodeScalar(0x2700)...UnicodeScalar(0x27BF), UnicodeScalar(0xFE00)...UnicodeScalar(0xFE0F) - Raphael
啊,是的,我应该在我的回答中包含一些内容,我只是应用了你问题中的表情符号范围,并解释了如何在我的回答中使用这些技巧,但并不知道这些范围是否全面!感谢您的编辑! - dfrib
@dfri Swift 4.1 let emojiPatterns = [UnicodeScalar(0x1F601)!...UnicodeScalar(0x1F64F)!, UnicodeScalar(0x2702)!...UnicodeScalar(0x27B0)!] - Leo Dabus
1
@LeoDabus 谢谢!我尝试了对上面解决方案的快速编辑,但在第二个块(6个模式)的emojiPatterns中遇到了“表达式过于复杂”的问题。这似乎很傻,几乎像是一种退化,但如果我有时间,我会尝试将其重构为编译器可以处理的内容。 - dfrib

7

Swift 4:

extension String {
  func stringByRemovingEmoji() -> String {
    return String(self.filter { !$0.isEmoji() })
  }
}

extension Character {
  fileprivate func isEmoji() -> Bool {
    return Character(UnicodeScalar(UInt32(0x1d000))!) <= self && self <= Character(UnicodeScalar(UInt32(0x1f77f))!)
      || Character(UnicodeScalar(UInt32(0x2100))!) <= self && self <= Character(UnicodeScalar(UInt32(0x26ff))!)
  }
}

7

表情符号被Unicode分类为符号。字符集通常用于搜索操作。因此,我们将使用字符集作为符号属性。

var emojiString =  "Hey there , welcome"
emojiString = emojiString.components(separatedBy: CharacterSet.symbols).joined()       
print(emojiString)

输出为

Hey there , welcome

现在观察,表情符号被替换为一个空格,所以有两个空格,我们可以通过以下方式进行替换。
emojiString.replacingOccurrences(of: "  ", with: " ") 

上述方法将参数中的“两个空格”替换为“单个空格”。

3
获取所有表情符号比你想象的要复杂。如需了解哪些字符是表情符号,请查看此stackoverflow帖子或者这篇文章
基于以上信息,我建议使用Character扩展更容易地让我们理解哪些字符是表情符号,然后添加一个String扩展来轻松替换发现的表情符号为另一个字符。
extension Character {
   var isSimpleEmoji: Bool {
      guard let firstProperties = unicodeScalars.first?.properties else {
        return false
      }
      return unicodeScalars.count == 1 &&
          (firstProperties.isEmojiPresentation ||
             firstProperties.generalCategory == .otherSymbol)
   }
   var isCombinedIntoEmoji: Bool {
      return unicodeScalars.count > 1 &&
             unicodeScalars.contains {
                $0.properties.isJoinControl ||
                $0.properties.isVariationSelector
             }
   }
   var isEmoji: Bool {
      return isSimpleEmoji || isCombinedIntoEmoji
   }
}

extension String {
    func replaceEmoji(with character: Character) -> String {
        return String(map { $0.isEmoji ? character : $0 })
    }
}

使用它将变得简单:
"Some string  with emoji".replaceEmoji(with: " ")

似乎无法使用标志。 - Alexandre G

0

我发现上面提供的解决方案对于某些字符(如️‍♂️和.)无效。

为了找到表情符号范围,我使用正则表达式将完整的表情符号列表转换为仅包含十六进制值的文件。然后我将它们转换为十进制格式并进行排序。最后,我编写了一个脚本来查找范围。

这是isEmoji()的最终Swift扩展。

extension Character {

    func isEmoji() -> Bool {
        let emojiRanges = [
            (8205, 11093),
            (12336, 12953),
            (65039, 65039),
            (126980, 129685)
        ]
        let codePoint = self.unicodeScalars[self.unicodeScalars.startIndex].value
        for emojiRange in emojiRanges {
            if codePoint >= emojiRange.0 && codePoint <= emojiRange.1 {
                return true
            }
        }
        return false
    }

}

供参考,这里是我编写的Python脚本,用于将十六进制字符串解析为整数,然后找到范围。

convert-hex-to-decimal.py

decimals = []
with open('hex.txt') as hexfile:
    for line in hexfile:
        num = int(line, 16)
        if num < 256:
            continue
        decimals.append(num)

decimals = list(set(decimals))
decimals.sort()

with open('decimal.txt', 'w') as decimalfile:
    for decimal in decimals:
        decimalfile.write(str(decimal) + "\n")

make-ranges.py

first_line = True
range_start = 0
prev = 0
with open('decimal.txt') as hexfile:
    for line in hexfile:
        if first_line: 
            prev = int(line)
            range_start = prev
            first_line = False
            continue

        curr = int(line)
        if prev + 1000 < curr: # 100 is abitrary to reduce number of ranges
            print("(" + str(range_start) + ", " + str(prev) + ")")
            range_start = curr
        prev = curr

看起来很有前途,但是还有一些误报 - Alexandre G

0
这个 Swift 5 片段对我来说非常好用,但我还没有检查所有的表情符号。不过它仍然可以显示中文、韩文等,而我的以前的解决方案则无法胜任。
extension String {
    // works with unicode languages like korean
    func removeEmojis() -> String {
        return .init(
            unicodeScalars.filter {
                $0.properties.isEmojiPresentation == false &&
                $0.properties.isEmoji == false
            }
        )
    }
}

-2
不要硬编码表情符号的范围,使用这个方法代替。
func 去除表情符号(字符串:String) -> String {
    let 转换为Unicode = 字符串.unicodeScalars//https://developer.apple.com/documentation/swift/string
    
    let 去除表情后的结果 = 转换为Unicode.filter { (item) -> Bool in
        let 判断是否表情 = item.properties.isEmoji
         return !判断是否表情//是表情就不保留
      }
    
    return String(去除表情后的结果)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接