我在屏幕上有一些文字:
Text("someText1")
Run Code Online (Sandbox Code Playgroud)
是否可以在不创建大量文本项目的情况下突出显示/选择部分文本
我的意思是
Text("som") + Text("eTex").foregroundColor(.red) + Text("t1")
Run Code Online (Sandbox Code Playgroud)
不是我的解决方案
最好有某种修饰符以某种方式突出显示文本的一部分。类似于:
Text("someText1").modifier(.highlight(text:"eTex"))
Run Code Online (Sandbox Code Playgroud)
是否可以?(我的意思是没有创造很多观点)
Rob*_*ier 12
创建文本后,您将无法再打开它。您的示例会产生本地化问题。someText1实际上不是要打印的字符串。它是字符串的本地化键。默认的本地化字符串恰好是键,所以它可以工作。eTex当您进行本地化时,您搜索的尝试会悄悄中断。所以这不是一个好的通用接口。
即便如此,构建解决方案还是很有启发性的,并且可能对特定情况有用。
基本目标是将样式视为应用于范围的属性。这正是 NSAttributedString 为我们提供的,包括合并和拆分范围以管理多个重叠属性的能力。NSAttributedString 对 Swift 不是特别友好,所以从头开始重新实现它可能会有一些价值,但相反,我只是将它作为实现细节隐藏起来。
因此 TextStyle 将是一个 NSAttributedString.Key 和一个将文本转换为另一个文本的函数。
public struct TextStyle {
// This type is opaque because it exposes NSAttributedString details and
// requires unique keys. It can be extended by public static methods.
// Properties are internal to be accessed by StyledText
internal let key: NSAttributedString.Key
internal let apply: (Text) -> Text
private init(key: NSAttributedString.Key, apply: @escaping (Text) -> Text) {
self.key = key
self.apply = apply
}
}
Run Code Online (Sandbox Code Playgroud)
TextStyle 是不透明的。为了构建它,我们公开了一些扩展,例如:
// Public methods for building styles
public extension TextStyle {
static func foregroundColor(_ color: Color) -> TextStyle {
TextStyle(key: .init("TextStyleForegroundColor"), apply: { $0.foregroundColor(color) })
}
static func bold() -> TextStyle {
TextStyle(key: .init("TextStyleBold"), apply: { $0.bold() })
}
}
Run Code Online (Sandbox Code Playgroud)
这里值得注意的是 NSAttributedString 只是“一个由范围内的属性注释的字符串”。它不是“样式字符串”。我们可以组成我们想要的任何属性键和值。所以这些属性与 Cocoa 用于格式化的属性不同。
接下来,我们创建 StyledText 本身。我首先关注这种类型的“模型”部分(稍后我们将使它成为一个视图)。
public struct StyledText {
// This is a value type. Don't be tempted to use NSMutableAttributedString here unless
// you also implement copy-on-write.
private var attributedString: NSAttributedString
private init(attributedString: NSAttributedString) {
self.attributedString = attributedString
}
public func style<S>(_ style: TextStyle,
ranges: (String) -> S) -> StyledText
where S: Sequence, S.Element == Range<String.Index>?
{
// Remember this is a value type. If you want to avoid this copy,
// then you need to implement copy-on-write.
let newAttributedString = NSMutableAttributedString(attributedString: attributedString)
for range in ranges(attributedString.string).compactMap({ $0 }) {
let nsRange = NSRange(range, in: attributedString.string)
newAttributedString.addAttribute(style.key, value: style, range: nsRange)
}
return StyledText(attributedString: newAttributedString)
}
}
Run Code Online (Sandbox Code Playgroud)
它只是一个 NSAttributedString 的包装器,也是一种通过将 TextStyles 应用于范围来创建新 StyledTexts 的方法。一些要点:
调用style不会改变现有对象。如果是这样,你就不能做这样的事情return StyledText("text").apply(.bold())。你会得到一个错误,该值是不可变的。
范围是棘手的事情。NSAttributedString 使用 NSRange,并且具有与 String 不同的索引概念。NSAttributedStrings 的长度可以与底层 String 不同,因为它们组合字符的方式不同。
您不能安全地String.Index从一个字符串中取出 a并将其应用于另一个字符串,即使这两个字符串看起来相同。这就是为什么这个系统采用闭包来创建范围而不是获取范围本身。attributedString.string与传入的字符串不完全相同。如果调用者想要传递Range<String.Index>,那么他们使用与 TextStyle 使用的字符串完全相同的字符串来构造它是至关重要的。这是最容易通过使用闭包来确保的,并避免了很多极端情况。
默认style接口处理一系列范围以获得灵活性。但在大多数情况下,您可能只会传递一个范围,因此有一个方便的方法是很好的,并且对于您想要整个字符串的情况:
public extension StyledText {
// A convenience extension to apply to a single range.
func style(_ style: TextStyle,
range: (String) -> Range<String.Index> = { $0.startIndex..<$0.endIndex }) -> StyledText {
self.style(style, ranges: { [range($0)] })
}
}
Run Code Online (Sandbox Code Playgroud)
现在,创建 StyledText 的公共接口:
extension StyledText {
public init(verbatim content: String, styles: [TextStyle] = []) {
let attributes = styles.reduce(into: [:]) { result, style in
result[style.key] = style
}
attributedString = NSMutableAttributedString(string: content, attributes: attributes)
}
}
Run Code Online (Sandbox Code Playgroud)
注意verbatim这里。此 StyledText 不支持本地化。可以想象,通过工作它可以,但需要更多的思考。
最后,毕竟,我们可以使它成为一个视图,方法是为每个具有相同属性的子字符串创建一个文本,将所有样式应用于该文本,然后使用+. 为方便起见,Text 直接暴露,因此您可以将其与标准视图结合使用。
extension StyledText: View {
public var body: some View { text() }
public func text() -> Text {
var text: Text = Text(verbatim: "")
attributedString
.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length),
options: [])
{ (attributes, range, _) in
let string = attributedString.attributedSubstring(from: range).string
let modifiers = attributes.values.map { $0 as! TextStyle }
text = text + modifiers.reduce(Text(verbatim: string)) { segment, style in
style.apply(segment)
}
}
return text
}
}
Run Code Online (Sandbox Code Playgroud)
就是这样。使用它看起来像这样:
// An internal convenience extension that could be defined outside this pacakge.
// This wouldn't be a general-purpose way to highlight, but shows how a caller could create
// their own extensions
extension TextStyle {
static func highlight() -> TextStyle { .foregroundColor(.red) }
}
struct ContentView: View {
var body: some View {
StyledText(verbatim: "??someText1")
.style(.highlight(), ranges: { [$0.range(of: "eTex"), $0.range(of: "1")] })
.style(.bold())
}
}
Run Code Online (Sandbox Code Playgroud)
您也可以将 UILabel 包装在 UIViewRepresentable 中,然后使用attributedText. 但这将是作弊。:D
免责声明:我真的很不愿意发布我的答案,因为我确信一定有很多更聪明、更好的方法(我不知道可能是使用 TextKit 的 UIKit 视图的包装器)和更强大的方法,但是......我觉得这是一个有趣的练习,也许有人真的可以从中受益。
所以我们开始:
我将创建一个包含字符串(用于渲染)和另一个包含我们的“匹配”文本的视图,而不是修饰符。
struct HighlightedText: View {
let text: String
let matching: String
init(_ text: String, matching: String) {
self.text = text
self.matching = matching
}
var body: some View {
let tagged = text.replacingOccurrences(of: self.matching, with: "<SPLIT>>\(self.matching)<SPLIT>")
let split = tagged.components(separatedBy: "<SPLIT>")
return split.reduce(Text("")) { (a, b) -> Text in
guard !b.hasPrefix(">") else {
return a + Text(b.dropFirst()).foregroundColor(.red)
}
return a + Text(b)
}
}
}
Run Code Online (Sandbox Code Playgroud)
我想代码是不言自明的,但简而言之:
现在,我们可以像这样使用它:
struct ContentView: View {
@State var matching: String = "ll"
var body: some View {
VStack {
TextField("Matching term", text: self.$matching)
HighlightedText("Hello to all in this hall", matching: self.matching)
.font(.largeTitle)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个(蹩脚的)gif 演示它在行动:
最后,如果您想知道我是如何在 Xcode 之外运行 SwiftUI 的,这里是我为在 Mac 上的 SwiftUI 中快速制作原型而制作的要点
我非常喜欢@Alladinian 的简单解决方案,但我需要一个不区分大小写的解决方案,例如用于输入字符的预输入突出显示。
这是我使用正则表达式的修改:
struct HighlightedText: View {
let text: String
let matching: String
let caseInsensitiv: Bool
init(_ text: String, matching: String, caseInsensitiv: Bool = false) {
self.text = text
self.matching = matching
self.caseInsensitiv = caseInsensitiv
}
var body: some View {
guard let regex = try? NSRegularExpression(pattern: NSRegularExpression.escapedPattern(for: matching).trimmingCharacters(in: .whitespacesAndNewlines).folding(options: .regularExpression, locale: .current), options: caseInsensitiv ? .caseInsensitive : .init()) else {
return Text(text)
}
let range = NSRange(location: 0, length: text.count)
let matches = regex.matches(in: text, options: .withTransparentBounds, range: range)
return text.enumerated().map { (char) -> Text in
guard matches.filter( {
$0.range.contains(char.offset)
}).count == 0 else {
return Text( String(char.element) ).foregroundColor(.red)
}
return Text( String(char.element) )
}.reduce(Text("")) { (a, b) -> Text in
return a + b
}
}
}
Run Code Online (Sandbox Code Playgroud)
例子:
struct ContentView: View {
@State var matching: String = "he"
var body: some View {
VStack {
TextField("Matching term", text: self.$matching)
.autocapitalization(.none)
HighlightedText("Hello to all in this hall", matching: self.matching, caseInsensitiv: true)
.font(.largeTitle)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3877 次 |
| 最近记录: |