1

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)
            }
        }
    }
}
Jake Loew
  • 81
  • 7
  • Does removing `reversed()` from `logs.indices.reversed()` significantly improve the performance? – Norman Apr 03 '23 at 21:36
  • 1
    @Norman - it does not. The performance difference can be observed when toggling visibility of a very large response vs a small one. The small one renders just fine. – Jake Loew Apr 03 '23 at 21:47
  • 1
    You can try truncating the text, only showing the first 50 characters, and after expanding, showing the rest. How long is the text? – Jalil Apr 04 '23 at 00:04
  • 1
    I read some where "You need fixed heights on your elements. Will take care of the performance issues", perhaps wrapping responseBody in it's own ScrollView. – Norman Apr 04 '23 at 01:26
  • @Jalil - it's quite long... a huge blob of text created from an API response. – Jake Loew Apr 04 '23 at 19:38
  • @Norman - I think having a fixed height would solve this. In that regard, perhaps this is a design issue than a code issue. I think I will truncate the text. – Jake Loew Apr 04 '23 at 19:53

2 Answers2

0

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 : "")
malhal
  • 26,330
  • 7
  • 115
  • 133
0

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.

Jake Loew
  • 81
  • 7