swiftuibundle-identifiermenubarextra

Getting Bundle Identifier on MenuBarExtra


I’m writing a MenuBarExtra which needs to know the current application. I have some code which nearly does the job:

import SwiftUI

@main
struct XBApp: App {
    @State var bundleID: String = ""

    var body: some Scene {
        MenuBarExtra("Something", systemImage: "questionmark.bubble") {
            VStack {
                XBContent(bundleID: bundleID)
                    .padding(0)
            }
            .onAppear {
                bundleID = NSWorkspace.shared.frontmostApplication!.bundleIdentifier!
                print("\(#fileID):\(#line) - \(bundleID)")
            }
        }
        .menuBarExtraStyle(.window)
    }
}

struct XBContent: View {
    var bundleID: String = ""
    @State var data: String = ""

    init(bundleID: String) {
        self.bundleID = bundleID    //  store received value
    }
    
    var body: some View {
        VStack {
            Text(bundleID)          //  current value
            Text(data)              //  previous value
        }
        .onAppear {
            data = bundleID         //  copy value
            print("\(#fileID):\(#line) - \(bundleID) | \(data)")
        }
    }
}

The main App sets the current Bundle id in .onAppear and this is then passed on to the XBContent view. I think this part is doing its job correctly, but I’m open to correction.

The XBContent view simply displays two versions of the passed bundle id:

Here’s the problem: the copied value from onAppear is always one step behind the other. When the app is first launched, and the menu bar extra is opened, the first value is correct, while the second is the default empty string; this is fixed the next time. When I switch to another app and open the menu bar extra, the first value is the correct new bundle id, while the second is the old one, until I try it again and it’s OK.

I gather from this that onAppear is not triggered when I thought it should be.

How can I get the second value to be the correct one?


Solution

  • Try this approach using @Binding var bundleID: String. Note no need for the init(...) in XBContent, the view does it all for you. Tested on real device macOS 15.

    @main
    struct XBApp: App {
        @State private var bundleID: String = "" // <--- here
    
        var body: some Scene {
            MenuBarExtra("Something", systemImage: "questionmark.bubble") {
                VStack {
                    XBContent(bundleID: $bundleID) // <--- here
                        .padding(0)
                }
                .onAppear {
                    bundleID = NSWorkspace.shared.frontmostApplication!.bundleIdentifier!
                    print("\(#fileID):\(#line) - \(bundleID)")
                }
            }
            .menuBarExtraStyle(.window)
        }
    }
    
    struct XBContent: View {
        @Binding var bundleID: String  // <--- here
        @State private var data: String = ""  // <--- here
    
        var body: some View {
            VStack {
                Text(bundleID)
                Text(data)
            }
            .onAppear {
                data = bundleID  //  copy value
                print("\(#fileID):\(#line) - \(bundleID) | \(data)")
            }
        }
    }
    

    Since you have data = bundleID, these two values will always be the same.