如何在 SwiftUI 中用图像替换占位符文本?

Asw*_*ath 5 swift swiftui

我有一个字符串"Hello {world}"需要替换为"Hello ". 占位符的位置最后并不固定。我可能有不止一个占位符。

我正在使用 SwiftUI 并尝试使其与

Text("Hello {world}".replacingOccurrences(of: "{world}", with: "\(Image(systemName: "globe"))"))
Run Code Online (Sandbox Code Playgroud)

但很快发现这不起作用并提出了这个Hello Image(provider: SwiftUI.ImageProviderBox<SwiftUI.Image.(unknown context at $1ba606db0).NamedImageProvider>)

既然这有效

Text(LocalizedStringKey("Hello \(Image(systemName: "globe"))"))
Run Code Online (Sandbox Code Playgroud)

我认为我需要将 a 传递LocalizedStringKeyText我再次尝试的

Text(LocalizedStringKey("Hello {world}".replacingOccurrences(of: "{world}", with: "\(Image(systemName: "globe"))")))
Text(LocalizedStringKey("Hello" + "\(Image(systemName: "globe"))")) //this doesn't work either
Run Code Online (Sandbox Code Playgroud)

但遇到了类似的问题SwiftUI.Text.Storage.anyTextStorage(SwiftUI.(unknown context at $1ba668448).LocalizedTextStorage

LocalizedStringKey我查看了和的 API LocalizedStringKey.StringInterpolation,但找不到解决此问题的方法。有没有办法可以替换占位符字符串?

Asw*_*ath 6

看了 @bewithyou 的答案后,我想到我需要将其拆分为多个子字符串并单独重新组合文本。这是我能想到的最好的解决方案:

public extension String {
    
    func componentsKeepingSeparator(separatedBy separator: Self) -> Array<String> {
        
        self.components(separatedBy: separator)
            .flatMap { [$0, separator] }
            .dropLast()
            .filter { $0 != "" }
    }
}
Run Code Online (Sandbox Code Playgroud)

在操场上,如果我运行它,它会完美运行。

PlaygroundPage.current.setLiveView(

    "Hello {world}!"
        .componentsKeepingSeparator(separatedBy: "{world}")
        .reduce(Text("")) { text, str in
            if str == "{world}" { return text + Text("\(Image(systemName: "globe"))") }
            return text + Text(str)
        }
)
Run Code Online (Sandbox Code Playgroud)

我确信有一个更优化的解决方案,但目前就这样。

编辑:

由于我需要支持多个占位符,因此我添加了一些扩展来更全面地完成这项工作。

func componentsKeepingSeparators(separatedBy separators: [Self]) -> [String] {
    
    var finalResult = [self]
    separators.forEach { separator in
        
        finalResult = finalResult.flatMap { strElement in
            
            strElement.componentsKeepingSeparator(separatedBy: separator)
        }
    }
    return finalResult
}
Run Code Online (Sandbox Code Playgroud)

以及在操场上

PlaygroundPage.current.setLiveView(
    "Hello {world}{world}{world}! {wave}"
        .componentsKeepingSeparators(separatedBy: ["{world}", "{wave}"])
        .reduce(Text("")) { text, str in
            if str == "{world}" { return text + Text("\(Image(systemName: "globe"))") }
            if str == "{wave}" { return text + Text("\(Image(systemName: "hand.wave"))") }
            return text + Text(str)
        }
)
Run Code Online (Sandbox Code Playgroud)

这个扩展有一个双循环,可能不是很有效,所以再次,如果有人能想到更好的解决方案,请发帖。


jrt*_*ton 4

我通过回答这个问题来提出这个问题,它激起了我的兴趣。正如我在回答中所说,秘密在于LocalizedStringKey当使用插值字符串文字初始化时,能够构建对 SwiftUIImage类型的引用,这些类型可以在Text.

因为您没有使用插值字符串文字,所以您可以通过多个 来构建内容Texts,如此处的其他答案所示,或者使用 做一些聪明的事情LocalizedStringKey.StringInterpolationLocalizedStringKey这种方法的优点是,您还可以在任何其他使用的视图(几乎是任何显示文本的视图)中使用图像保存文本。

此扩展LocalizedStringKey将手动构建一个内插字符串:

extension LocalizedStringKey {

    private static let imageMap: [String: String] = [
        "world": "globe",
        "moon": "moon"
    ]

    init(imageText: String) {
        var components = [Any]()
        var length = 0
        let scanner = Scanner(string: imageText)
        scanner.charactersToBeSkipped = nil
        while scanner.isAtEnd == false {
            let up = scanner.scanUpToString("{")
            let start = scanner.scanString("{")
            let name = scanner.scanUpToString("}")
            let end = scanner.scanString("}")
            if let up = up {
                components.append(up)
                length += up.count
            }
            if let name = name {
                if start != nil, end != nil, let imageName = Self.imageMap[name] {
                    components.append(Image(systemName: imageName))
                    length += 1
                } else {
                    components.append(name)
                }
            }
        }

        var interp = LocalizedStringKey.StringInterpolation(literalCapacity: length, interpolationCount: components.count)
        for component in components {
            if let string = component as? String {
                interp.appendInterpolation(string)
            }
            if let image = component as? Image {
                interp.appendInterpolation(image)
            }
        }

        self.init(stringInterpolation: interp)
    }
}
Run Code Online (Sandbox Code Playgroud)

如果这些值来自 API,您可能想要缓存它们,我尚未在渲染循环中检查此代码的性能。

Text您可以在或任何其他视图上添加扩展:

extension Text {
    init(imageText: String) {
        self.init(LocalizedStringKey(imageText: imageText))
    }
}
Run Code Online (Sandbox Code Playgroud)

所以你可以这样做:

Text(imageText: "Hello {world}! or {moon} or {unmapped}")
Run Code Online (Sandbox Code Playgroud)

这给你:

带有插值图像的文本视图