Reading view

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

Last Week on My Mac: Plan ahead with this summer’s mallyshag

Summer is an unpredictable time of year. With the Atlantic hurricane season already upon us, we could see searing heat or devastating storms. So it is with the announcements made at WWDC earlier this month: do we have time to try out some of the new features coming in three months, or must we get on with wrangling deprecations and changes looming in macOS Tahoe?

A glance through Apple’s beta release notes might suggest it should prove innocuous, and the great majority of code that’s already happy in Sequoia should have no problems in Tahoe, and so far that’s my experience. That should leave us plenty of time to adjust our app icons so they display properly in the Dock and elsewhere, but it’s there it gets more subtly complicated.

Fix app icons

I don’t think I can over-stress the importance of using Icon Composer for creating replacement app icons. If you don’t, then Tahoe seems determined to deface many traditional icons so they become almost illegible and unusable. The only exemptions are those already conforming to the fixed outline of a square with rounded corners. Any irregularity such as putting a pixel outside that, and they’re relegated to the sin bin.

Here are two icons for the same app viewed in Tahoe. The left one uses a traditional AppIcon.icns icon image, while that on the right is the same circular PNG that has been applied using Icon Composer and added as a .icon file. So far my attempts to get this to work using Xcode 16.4 have been unsuccessful, and the only solution has been to use a beta-release of Xcode 26.

Overhaul controls

That brings with it another problem, as it automatically converts AppKit and SwiftUI layouts so they use Tahoe’s new interface style, and that can generate further work. If you look closely at Apple’s demos of Tahoe at WWDC, you may notice that its controls have changed in size and shape. Not only do most have more rounded corners, but they also have different dimensions.

Interface conversion for apps that use AppKit or SwiftUI is clever, as it preserves the original for use in previous versions of macOS, and only adopts the new style when in Tahoe. Build your app with its smart new Tahoe-compatible icon and run it in Sequoia, and it looks just the same as it did.

This demo, Mallyshag, looks the same in Sequoia, but has become a mess in Tahoe because of those changed control dimensions.

Those three buttons are significantly wider, so now overlap one another and are wider than the text box below. They need a careful overhaul before they’re ready for Tahoe. Conversion can also have unexpected side-effects: for example, I’ve had some selectable text fields changed to be editable as well. You can see an example that I missed in the left view in XProCheck’s window. I now check carefully through every detail in windows that have been migrated by Xcode to support Tahoe.

This doesn’t just apply to AppKit windows in Interface Builder. Although SwiftUI dynamically positions controls, I’ve found it necessary to increase the minimum width of some views to ensure they remain fully usable.

Aside from any code changes needed, migrating an app to Tahoe thus requires:

  • creation of a new app icon using Icon Composer;
  • adding the .icon file to the Xcode project and setting it as the app icon;
  • careful checking and rectification of all windows and their contents.

NSLog

There’s one last thing that may have escaped your attention in Apple’s release notes: NSLog. When Apple introduced the Unified log in macOS Sierra, it preserved the longstanding use of NSLog as a means of making entries with a minimum of fuss. More formal methods are more cumbersome, although they’re also more powerful, so NSLog still remains popular with developers, at least until Tahoe’s change.

A long way down the release notes, and oddly announced under the subheading of New Features, Apple states that NSLog will no longer record anything of use in its entries in the Unified log, although they’ll still be reported in full in Xcode and to stdout. One of the other purposes of my test app Mallyshag was to verify just what is now recorded by NSLog.

This is the entry obtained using LogUI when running either version of the app in macOS 15.5:

And this is the extent of entries seen in macOS 26:

So what in earlier macOS might have been a useful
Error number 1467296 in Mallyshag
is redacted to the contentless stub <private>.

If you still use NSLog, you’ll almost certainly want to move on to a better alternative, again being careful to avoid ending up with its contents redacted.

Outcomes

Come the release of macOS 26 Tahoe, there’ll be three groups of apps:

  • those that haven’t been ported at all, whose icons will be almost unrecognisable;
  • those whose icons display correctly, but with flaws in interface controls;
  • those that work as expected, with conformant icons and controls.

Some will also write dysfunctional messages in the log, because they’re still using NSLog, although few users are likely to notice that.

That doesn’t take into account those apps relying on alternatives to AppKit and SwiftUI for their interface, as those have a great deal of ground to cover in just a few months if they’re going to be ready in time for Tahoe’s release.

That’s why I’ve started unusually early in getting my apps ready for the autumn/fall. I’m sure that summer still has some surprises in store.

Mallyshag?

This is a local Isle of Wight name for a caterpillar, usually a large and hairy one. It just seemed appropriate.

merianlappet
Maria Sibylla Merian (1647–1717), Metamorphosis of the Lappet (after 1679), watercolour, 19.3 x 15.9 cm, Städelsches Kunstinstitut und Städtische Galerie, Frankfurt am Main, Germany. Wikimedia Commons.

Last Week on My Mac: Who’s afraid of changing interface?

With Apple’s annual Worldwide Developers Conference less than a month away, speculation about what’s coming in macOS 16 is starting to warm up. So far that has concentrated on increasing consistency in interfaces across the different platforms, which could mean almost anything. As far as macOS is concerned, that’s largely up to AppKit and SwiftUI, its two major interface libraries.

AppKit remains widely used, and is still the more complete of the two. Descended from the UI framework in NeXTSTEP, it was in the core of Mac OS X at the start, and has been the mainstay for the Finder and Apple’s own apps for the last 25 years. It has a close relative in UIKit for iOS and iPadOS, although they are less comprehensive in their features.

SwiftUI is an interesting experience for the macOS developer, and is currently an archipelago of delights in a sea of disappointment. Some of its features are powerful, but a great deal is still lacking. Support for views and features widely used in modern iOS and iPadOS apps is impressive, and it opens up features such as the List View that I praised recently. But when it comes to essentials that are confined to macOS, such as menus, a great deal of work remains as it comes up to its sixth birthday on 3 June.

This is demonstrated in one of the best tutorials I’ve seen on using SwiftUI for macOS, in this case to develop a Markdown editor, written by Sam Rowlands of Ohanaware. No sooner has he set up a split view to accommodate both the Markdown source and its preview in the same window, than he writes: “The TextEditor in SwiftUI ticks the box of offering a way to edit a large volume of text, but that’s about all it does. Apple have a much more powerful text editor already in the macOS as part of their AppKit framework, so we’re going to wrap that instead.”

This was my experience a while ago when I looked at a range of document formats. Open the Help book in LogUI and what you see there is cast not in SwiftUI, which still doesn’t offer a PDF view, but reaches back to AppKit. While creating a useful Rich Text editor using AppKit is amazingly quick and simple, even plain text editing in SwiftUI is feeble. There are plenty of experts who will advise you “SwiftUI’s text editor is very limited. It doesn’t support much more than entering large amounts of plain text. If you want rich text editing, you will have to use either NSTextView or UITextView.”

These are fundamental features that should by now be easy going for any macOS interface library that’s six years old.

Continuing dependence on both AppKit and SwiftUI presents Apple with the problem of having to update both to reflect changes it intends making to improve interface consistency, then for iOS there’s also UIKit. Not only that, but all three libraries have to integrate and work together.

SwiftUI has undergone constant change over those six years. One of its most substantial changes has been the move from the Observable Object protocol to the Observable macro. Apple describes how to migrate in this article, complete with sample code. But that poses the developer a problem, as adopting the latter is only possible in apps written for macOS 14 or iOS 17 or later. That’s why LogUI requires a minimum of macOS 14.6, as do many of the better SwiftUI apps. Writing SwiftUI apps to support macOS older than Sonoma and Sequoia is thus a serious undertaking, and whatever macOS 16 and its new version of SwiftUI bring, you can be sure they’ll make backward compatibility even more impractical.

Documentation for SwiftUI is also broken. Apple seems to have stopped writing conceptual explanations about ten years ago, and structured guides have been replaced by terse and usually uninformative references to individual functions and other details of the macOS API. The only way to try to gain understanding of SwiftUI is to turn to third-parties, who are more interested in the lucrative iOS market rather than macOS, and think a series of example projects are a substitute for a systematic guide.

If there’s one sound investment for the future that Apple could make from its much-vaunted half trillion dollar investment in the US, it would be to hire a large team of technical authors and catch up with its ten-year backlog of documentation.

Whether you gasp with horror or delight when Apple reveals what’s coming in macOS 16 next month, spare a thought for all the changes that have to take place in AppKit, UIKit and SwiftUI, all the documentation that won’t get written, and how code is going to struggle to be compatible with macOS 16 and Sequoia or earlier. Then for good measure throw in the inevitable load of new bugs. So you still want to beta-test macOS 16?

Why I like SwiftUI List Views

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

❌