My goal is to create a SwiftUI view that takes a String and automatically formats that text into Text views. The portion of the string that needs formatting is found using regex and then returned as a Range<String.Index>. This can be used to reconstruct the String once the formatting has been applied to the appropriate Text views. Since there could be multiple instances of text that needs to be formatted, running the formatting function should be done recursively.
struct AttributedText: View {
@State var text: String
var body: some View {
AttributedTextView(text: text)
}
@ViewBuilder
private func AttributedTextView(text: String) -> some View {
if let range = text.range(of: "[0-9]+d[0-9]+", options: .regularExpression) {
//The unattributed text
Text(text[text.startIndex..<range.lowerBound]) +
//Append the attributed text
Text(text[range]).bold() +
//Search for additional instances of text that needs attribution
AttributedTextView(text: String(text[range.upperBound..<text.endIndex]))
} else {
//If the searched text is not found, add the rest of the string to the end
Text(text)
}
}
I get an error Cannot convert value of type 'some View' to expected argument type 'Text'
, with the recommended fix being to update the recursive line to AttributedTextView(text: String(text[range.upperBound..<text.endIndex])) as! Text
. I apply this fix, but still see the same compiler error with the same suggested fix.
A few workarounds that I've tried:
- Changing the return type from
some View
toText
. This creates a different errorCannot convert value of type '_ConditionalContent<Text, Text>' to specified type 'Text'
. I didn't really explore this further, as it does make sense that the return value is reliant on that conditional. - Returning a Group rather than a Text, which causes additional errors throughout the SwiftUI file
Neither of these solutions feel very "Swifty". What is another way to go about this? Am I misunderstanding something in SwiftUI?
There are a few things to clarify here:
The
+
overload ofText
only works betweenTexts
which is why it's saying it cannot convertsome View
(your return type) toText
.Text
+Text
==Text
,Text
+some View
==☠️
Changing the return type to
Text
doesn't work for you because you're using@ViewBuilder
, remove@ViewBuilder
and it'll work fine.Why?
@ViewBuilder
allowsSwiftUI
to defer evaluation of the closure until later but ensures it'll result in a specific view type (not AnyView). In the case where your closure returns either aText
or anImage
this is handy but in your case where it always results inText
there's no need,@ViewBuilder
forces the return type to beConditionalContent<Text, Text>
so that it could have different types.Here's what should work:
I made it static too because there's no state here it's a pure function and lowercased it so it was clear it was a function not a type (the function name looks like a
View
type).You'd just call it
Self.attributedTextView(text: ...)