Conditionally rendering Text view with a large string causes performance issue

329 Views Asked by At

My SwiftUI app has a network log screen that shows network requests sent and recorded by the app. There is a button to toggle the visibility of each request. Requests are serialized and stored as strings, so they can be displayed on this screen.

Some of the response objects are very large — as response objects tend to be sometimes — which is causing a 1-2 second delay when the “toggle visibility” button is pressed. Is there a way to optimize the performance of the Text view that renders the content?

struct NetworkLogsScreen: View {
    var logs: [NetworkLogEntry]

    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(logs.indices.reversed(), id: \.self) { index in
                    LogItem(logItem: logs[index])
                }
            }
        }
        .navigationTitle("Network Requests")
    }
}

struct LogItem: View {
    var logItem: NetworkLogEntry

    @State var isExpanded: Bool = false

    var body: some View {
        VStack {
            HStack {
                Text(logItem.timestamp.formatted())
                Spacer()
                Button {
                    self.isExpanded.toggle()
                } label: {
                    Text("toggle visibility")
                }
            }
            if self.isExpanded {
                Text(logItem.responseBody)
            }
        }
    }
}
2

There are 2 best solutions below

0
Jake Loew On BEST ANSWER

The fix:

Ended up using a substring of the actual content. This fixed the issue. So you can no longer see the entire content. I added a button that copies the entire response to your clipboard and this satisfied my needs. The UIPasteboard functionality hangs sometimes, but I supposed that's what you get for copying 1500+ lines of text.

I did see some improvement from using a .frame modifier setting the maxHeight and adding a truncationMode to that, but it did not completely fix the issue.

So the modified code is basically...

if self.isExpanded {
    Text(logItem.responseBody.prefix(2000))
}

Would love to figure out if there's a way to fix the issue without modifying the user-facing options.

5
malhal On

ForEach is not a for loop it shouldn't be used with indices and the id: \.self is a mistake. Furthermore the logs[index] inside ForEach's content closure can actually cause a crash with an index out of range exception. You need to fix it to be like:

ForEach(logs.reversed()) { logItem in
    LogItem(logItem: logItem)
}

You can achieve this by making NetworkLogEntry conform to Identifiable or specify an id param that is a unique property of the model struct not the struct itself because that is not unique across mutations of the logs array.

You could also fix your date formatting:

Text(logItem.timestamp format: .date)

Replace ScrollView and LazyVStack with List.

Change var to let.

Remove the if isExpanded and change the Text to:

Text(isExpanded ? logItem.responseBody : "")