Customizing the macOS About Panel in SwiftUI

Nov 28, 2023 · Follow on Twitter and Mastodon

In this post, we’ll take a look at how we can customize the macOS About Panel in SwiftUI, using project settings, bundle files, and SwiftUI.

As an example, let’s create a brand new SwiftUI app and call it “MyApp”. When running the app, the main menu will have a default “About…” item that opens the app’s about panel:

The default about panel

This panel will by default show the app icon, name, build number and version number. If we add an app icon to our app, it will automatically be applied without any code needed.

There are however things that we may want to adjust. For instance, the app name should be “My App”. We may also want to display additional information, like copyright.

How to customize the app display name

The project name “MyApp” is the default display name for the app. Let’s change the display name to “My App” in Project Settings to see what happens.

We can either do this under the “Info” tab:

A screenshot of how to change display name in Info

or in Build Settings:

A screenshot of how to change display name in Info

If we run the app with this new display name, the main menu and about panel will still say “MyApp”, while the menu item that opens the about panel says “About My App”.

A screenshot of how to change display name in Build Settings

So, the main menu and about panel use the project name and ignores the display name.

While there may be ways to fix this in settings, let’s take it as a reason to look at how we can customize the About Panel to show custom content, with and without code.

How to customize the about panel without code

Although I haven’t found a way to change the app display name in the about panel without code, there are some things that you can change with build settings and files.

Any copyright text that you add in Build Settings automatically appears in the about panel:

A screenshot of how to change copyright information

If you add this, the about panel will show the information like this:

A screenshot of how copyright is presented in the about panel

Credits

You can also provide custom credits (credits to @troz and @casecollection) for the app, by adding a credits.rtf or credits.html file to your app.

Here, we add an RTF file with some rich text content:

A screenshot of how to add an RTF file

If you add this, the about panel will show the information like this:

A screenshot of how credits are presented in the about panel

However, if you want the credits to contain dynamic content, you need to define this info with code, where you can inject any variables and data into the text.

How to customize the about panel with code

To customize the About Panel with code, we’ll use the same techniques as we looked at in the previous post on how to customize the main menu commands of an app.

We must replace the default about button with one that calls an NSApplication function called orderFrontStandardAboutPanel, that opens an About Panel with custom options:

@main
struct MyAppApp: App {
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            CommandGroup(replacing: .appInfo) {
               Button("About My App") {
                    NSApplication.shared
                       .orderFrontStandardAboutPanel(
                            options: [.applicationName: "My App"]
                        )
                }
            }
        }
    }
}

The code above replaces the .appInfo menu item with a custom menu item that opens an About Panel that overrides the app name. The result looks like this:

A custom about panel

We can use these options to override properties like applicationInfo, applicationName and applicationVersion, as well as credits and version (build number):

Customization options

The version row disappears if you set applicationVersion and version to empty strings and adjusts itself if you set either value to an empty string.

The credits value is presented below the version. Notice that this is an attributed string. The app will actually crash if you set it to a plain string.

The code below customizes the about panel’s credit text with an attributed string:

@main
struct MyAppApp: App {
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            CommandGroup(replacing: .appInfo) {
               Button("About My App") {
                    NSApplication.shared
                       .orderFrontStandardAboutPanel(
                        options: [
                            .applicationName: "My App",
                            .credits: NSAttributedString(
                                string: "This amazing app was created in SwiftUI.\nCopyright ©2023. No rights reserved.",
                                attributes: [
                                    .foregroundColor: NSColor.secondaryLabelColor,
                                    .font: NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
                                ]
                            )
                        ]
                    )
                }
            }
        }
    }
}

Yikes, I don’t like that indentation! Let’s specify it in another way to make the code cleaner:

@main
struct MyAppApp: App {
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            CommandGroup(replacing: .appInfo) {
               Button("About My App") {
                    NSApplication.shared
                       .orderFrontStandardAboutPanel(
                        options: .basic(
                            applicationName: "My App",
                            credits: "..."
                        )
                    )
                }
            }
        }
    }
}

public extension Dictionary 
    where Key == NSApplication.AboutPanelOptionKey, Value == Any {
    
    static func basic(
        applicationName: String,
        credits: String
    ) -> Self {
        [
            .applicationName: applicationName,
            .credits: NSAttributedString(
                string: credits,
                attributes: [
                    .foregroundColor: NSColor.secondaryLabelColor,
                    .font: NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
                ]
            )
        ]
    }
}

If we run the app again, we now have custom credits with a custom style in place as well:

Custom credits

We can improve this further, by creating a command that can be reused in many apps:

public struct AboutPanelCommand: Commands {
    
    public init(
        title: String,
        applicationName: String = Bundle.main.displayName,
        credits: String? = nil
    ) {
        let options: [NSApplication.AboutPanelOptionKey: Any]
        if let credits {
            options = [
                .applicationName: applicationName,
                .credits: NSAttributedString(
                    string: credits,
                    attributes: [
                        .foregroundColor: NSColor.secondaryLabelColor,
                        .font: NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
                    ]
                )
            ]
        } else {
            options = [.applicationName: applicationName]
        }
        self.init(title: title, options: options)
    }
    
    public init(
        title: String,
        options: [NSApplication.AboutPanelOptionKey: Any]
    ) {
        self.title = title
        self.options = options
    }
    
    private let title: String
    private let options: [NSApplication.AboutPanelOptionKey: Any]
    
    public var body: some Commands {
        CommandGroup(replacing: .appInfo) {
            Button(title) {
                NSApplication.shared
                    .orderFrontStandardAboutPanel(options: options)
            }
        }
    }
}

public extension Bundle {
    
    var displayName: String {
        infoDictionary?["CFBundleDisplayName"] as? String ?? "-"
    }
}

This lets us reduce the amount of code in our app to the following:

@main
struct MyAppApp: App {
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            AboutPanelCommand(
                title: "About My App",
                credits: "This amazing app was created in SwiftUI...."
            )
        }
    }
}

In the code above, we use the simplified initializer to define credits as plain text. If we want to specify a custom atttributed string, we have that option as well.

Conclusion

I really like SwiftUI’s APIs for working with menu commands, but the About Panel stuff is a bit hidden. I hope that they bring a more native approach in a future SwiftUI update.

With that, I’d love to see the limitations of the attributed string capabilities. The first to make Doom run in the credits text field (someone made it run in the touch bar) wins a prize.

Discussions & More

If you found this interesting and would like to share your thoughts, please comment in the Disqus section below or reply to this tweet or this toot.

Follow on Twitter and Mastodon to be notified when new content & articles are published.