我的应用程序中有一个文本字段需要限制其字符数。它适用于常规文本。但我需要考虑特殊字符和表情符号,当文本字段达到限制时,我删除一个字符,然后重复输入例如 ¢ (2 字节)或 € (3 字节),它的行为就好像没有字符一样完全没有限制,您可以输入任何内容。
TextField("Username", text: $ame)
.onChange(of: name, perform: { value in
totalBytes = value.utf8.count
// Only mess with the value if it is too big
if totalBytes > 14 {
let firstNBytes = Data(name.utf8.prefix(14))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the message back to the last place where it was the right size
name = maxBytesString
} else {
print("not a valid UTF-8 sequence")
}
}
})
控制台确实会打印“无效的 UTF-8”警告,但在这种边缘情况下,文本字段永远不会得到纠正。有什么帮助吗?顺便说一句,“name”是一个 @State 变量,如果重要的话,可以使用
onAppear
方法成功初始化。
问题在于
let firstNBytes = Data(name.utf8.prefix(14))
。正如您所看到的,某些字符超过 1 个字节。当您仅获取第一个 14 个字节时,您可以轻松地将一个字符切成两半。这就是为什么尝试从截断的字节创建 String
可能会失败。
您需要一种方法来逐字节修剪数据,从 14 开始,直到获得有效数据。您最终得到的字符串可能只有 13、12、11 甚至更少字节,但至少有一个小于 14 字节的字符串。
这是
String
的扩展,它将根据提供的字节限制返回截断的字符串:
extension String {
func truncated(to bytes: Int, encoding: String.Encoding = .utf8) -> String? {
if let data = self.data(using: encoding) {
var tmpData = data.prefix(bytes)
repeat {
if let res = String(data: tmpData, encoding: encoding) {
return res
} else {
tmpData = tmpData.dropLast(1)
}
} while !tmpData.isEmpty
}
return nil // Should never be reached with UTF-8 encoding
}
}
这样你的代码就会变成如下所示:
TextField("Username", text: $name)
.onChange(of: name, perform: { value in
totalBytes = value.utf8.count
// Only mess with the value if it is too big
if totalBytes > 14 {
if let newName = value.truncated(to: 14) {
name = newName
}
}
})