I have a log view.
I want to display the log lines in alternating background colors.
I want to allow text selection across the whole log view. This can be done with .textSelection(.enabled)
on a single Text
view.
My problem is however, that to get the alternating background colors, I am breaking my string into lines and show them in a ScrollView
(or List
). When I add .textSelection(.enabled)
, I can only select text in a single line at once.
How can I select across the whole text?
This is the code:
struct LogView: View {
let lines: [String] = sampleText3.components(separatedBy: "\n")
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 0) {
ForEach(lines.indices, id: \.self) { index in
HStack() {
Text(lines[index])
.foregroundColor(adaptiveLineColor(lines[index]))
.padding(.horizontal, 5)
Spacer()
}
.background(index % 2 == 0 ? Color.gray.opacity(0.1) : Color.black.opacity(0.1))
}
}
.textSelection(.enabled)
.border(.gray.opacity(0.1),width: 1)
}
.padding()
}
func adaptiveLineColor(_ line: String) -> Color {
enum Tag: String {
case task = "[Task]"
case info = "[info]"
case warning = "[warning]"
case error = "[error]"
}
// TODO: Use switch
if line.hasPrefix(Tag.task.rawValue) {
return .green
}
if line.hasPrefix(Tag.info.rawValue) {
return .primary
}
if line.hasPrefix(Tag.warning.rawValue) {
return .yellow
}
if line.hasPrefix(Tag.error.rawValue) {
return .red
}
return .primary
}
}
let sampleText3: String = """
[Task] Process started. 30. Sep 2023, 21:26:20
[info] input file /someFolder/someData
[warning] invalid metadata
[error] error -1234
"""
Screenshot:
If I put the whole text in one multiline Text
, textSelection
works as expected, but I wouldn't know how to do the alternating background colors.
How can I solve this?
I found this question, but it has no solution.
A really simple solution (though this is a bit of a hack) is to draw twice. First draw the text as many separate Text
s, each with their own background, and with a transparent foreground. Then, draw the entire string as a single Text
as an overlay. The user can then select the overlay.
ScrollView {
let attrText = makeAttributedString(sampleText3) // add all the different foreground colors as an AttributedString
VStack(alignment: .leading, spacing: 0) {
// lay out the texts with transparent foreground and alternating backgrounds
let invisibleTexts = lines.map { Text($0).foregroundColor(.clear) }
ForEach(lines.indices, id: \.self) { index in
HStack {
invisibleTexts[index]
.padding(.horizontal, 5)
Spacer()
}
.background(index % 2 == 0 ? Color.gray.opacity(0.1) : Color.black.opacity(0.1))
}
}
.overlay {
// lay out the selectable text in the same way
HStack {
Text(attrText).textSelection(.enabled).padding(.horizontal, 5)
Spacer()
}
}
.border(.gray.opacity(0.1),width: 1)
}