Normal view

There are new articles available, click to refresh the page.
Before yesterdayMain stream

Why I like SwiftUI List Views

By: hoakley
18 April 2025 at 14:30

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.

tuneperf2

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.

logui00

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

ulbow1b103

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.

unhidden1

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)
            }
        }
    }

Last Week on My Mac: Bring back the magic

By: hoakley
30 March 2025 at 15:00

One of the magic tricks that characterised the Mac was its association between documents and their apps. No longer did a user have to type in both the name of the app and the document they wanted it to edit. All they needed to do was double-click the document, and it magically opened in the right app.

In Classic Mac OS, that was accomplished by hidden Desktop databases and type and creator codes. For example, a text document might have the type TEXT and a creator code of ttxt. When you double-clicked on that, the Finder looked up which app had the creator code ttxt, which turned out to be the SimpleText editor, and opened that document using that app.

Although those ancient type and creator codes still live on today in modern macOS, they no longer fulfil that role. Instead, each file has what used to be a Uniform Type Indicator (UTI), now wrapped into a UTType, such as public.plain-text, normally determined by the extension to its name, .txt or .text. When you double-click on a file, LaunchServices looks up that UTType in its registry, discovers which app is set as the default to open documents of that type, then launches that app with an AppleEvent to open the document you picked.

Recognising that we often want to open a document using a different app rather than the default, the Finder’s contextual menu offers a list of suitable apps in its Open With command. That list is built and maintained by LaunchServices, and has changed in recent versions of macOS. Whereas those lists used to consist of apps installed in the traditional Application folders, LaunchServices now scours every accessible volume and folder using Spotlight’s indexes to build the biggest lists possible. If you happen to have an old copy of an app tucked away in a dusty corner, LaunchServices will find it and proudly display it alongside those in everyday use, like a game dog triumphantly presenting not one dead pheasant but every one from miles around.

For those with lean systems, this gives them the flexibility to open a large text document using BBEdit rather than TextEdit, or to select which image editor to use for a JPEG. But for those of us with lots of apps lurking in storage, the result is absurd and almost unusable. It’s bad enough working through the 33 apps that LaunchServices lists as PNG editors, but being offered 70 text editors is beyond a joke.

Unfortunately, there’s no lasting way to block unwanted apps from being added to the list LaunchServices builds for this Open With feature. You can gain temporary relief by excluding them from Spotlight search, but should you ever open the folder they’re in using the Finder, those are all added back. This also afflicts apps in folders shared with a Virtual Machine, where the list includes App Store apps that can’t even be run from within that VM.

There are, of course, alternatives. I could drag and drop the document from its Finder window towards the top of my 27-inch display to the app’s icon in the Dock at the foot, which is marginally less awkward than negotiating my way through that list of 70 apps.

But there are better solutions: why not empower me to determine which of those 70 apps should be offered in the Open With list? This is such a radical idea that it used to be possible with the lsregister command that has become progressively impotent, as LaunchServices has cast its net further in quest of more apps to flood me with. Or maybe use a little machine learning to include only those text editors I use most frequently to open documents? Apple could even brand that LaunchServices Intelligence, although that’s a little overstated.

I can’t help but think of what those magicians from forty years ago would have done, but I’m certain they wouldn’t have offered me that list of 70 apps to choose from.

❌
❌