Why I like SwiftUI List Views

0

Presenting complex verbal information in an accessible way remains one of the great challenges in interface design. This article explains how SwiftUI List Views offer solutions that are superior to those provided in AppKit, and why I have chosen to use them in several of my recent apps, such as LogUI and AppexIndexer.

Each of the apps I discuss here has a common problem: it has to display a series of text records consisting of fields whose contents can be completely empty or expand into long paragraphs. Thus the condensed length of each row, representing a single record, can be as short as 30 characters, or as long as hundreds, some with embedded line-breaks. Fields contain contrasting data, such as datestamps, flowing text and UUIDs. The user needs to scan rows rapidly, following patterns in fields and their contents, and to read each carefully.

There are some ground rules, notably:

rows must remain visually separate,
the order of fields in each row must be the same,
each field must be displayed in full and not truncated,
fields must be visually distinct across each row,
field width must be adjusted automatically to avoid horizontal scrolling.

There are centuries of experience in print design of tables, to which computers add dynamic resizing of columns and ready use of colour.

In some circumstances, AppKit’s Table View works well, here in Activity Monitor. However, adjustable column widths can’t overcome one problem shown here, where uniform column width wastes space for short process names, and truncates others.

This shows how poorly a Table View copes with log entries in Console.

My best solution using AppKit is for rows of untruncated text, with colour used to distinguish fields within them. Unfortunately, some rows inevitably overflow into multiple lines, and may require very wide windows to remain accessible.

This is SwiftUI’s List View in action with a log excerpt in LogUI. Although it might appear desirable to allow manual adjustment of field width, it’s more practical to provide options to change which fields are displayed, and so accommodate narrower windows. Sometimes line breaking in fields isn’t good, but I think this is a problem of content (computing terms being formed by concatenating series of words) rather than view design.

This is AppexIndexer, where I use an easily distinguished emoji to mark a significant field positioned in the middle of some rows. This aids navigation when scanning rows quickly, but doesn’t rely on the reading of the emoji.

Here’s another example, this time using many icons drawn from SF Symbols rather than pure text. This works well with fewer rows, but rendering many of those icons in thousands of rows may not be as efficient.

For macOS, SwiftUI continues to suffer serious omissions, and in some circumstances isn’t yet a good fit. But its List Views are a compelling reason for using SwiftUI in many native Mac apps. Further details are given in the references below, and the Appendix provides example source code for a basic implementation in SwiftUI.

Apple Developer Documentation

SwiftUI List view
SwiftUI Table view
AppKit NSTableView

Appendix: Example source code

struct ContentView: View {
@State private var messageInfo = Logger()

var body: some View {
let _ = messageInfo.getMessages()
if (self.messageInfo.logList.count > 0) {
VStack {
List(self.messageInfo.logList) { line in
MessageRow(line: line)
}
}
.frame(minWidth: 900, minHeight: 200, alignment: .center)
} else {
Text(“No results returned from the log for your query.”)
.font(.largeTitle)
}
}
}

struct MessageRow: View {
let line: LogEntry

var body: some View {
HStack {
Text(line.date + ” “)
if #available(macOS 14.0, *) {
Text(“(line.type)”)
.foregroundStyle(.red)
} else {
Text(“(line.type)”)
.foregroundColor(.red)
}
if !line.activityIdentifier.isEmpty {
Text(line.activityIdentifier)
}
// etc.
Text(line.composedMessage)
}
}
}

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.