How do I disable the Show Tab Bar menu option in SwiftUI
Asked Answered
C

5

16

I have created a very simple app on MacOS created with SwiftUI. Default there is a menu item show tab bar. How do I remove this? I doesn't make sense to have tabs in this app.

I've found the following answering the same question, but for older versions of Swift, not for SwiftUI: How do I disable the Show Tab Bar menu option in Sierra apps?

Caw answered 26/12, 2020 at 20:31 Comment(0)
O
7

I was looking for an answer for this as well and found out the following:

by default - as you already mentioned - the Show/Hide Tab is active:

enter image description here

There is a property on NSWindow called tabbingMode which allows us to take control by setting it to .disallowed. My problem though was: in a SwiftUI 2-lifecycle app, how can I get hold of the windows of the app?

Well, there's NSApplication.shared.windows, so my first (non working!!) attempt was to modify all the windows in my @main-App struct (as I already prevented new windows from being created, that should be suffice):

import SwiftUI

@main
struct DQ_SyslogApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear {
                    let _ = NSApplication.shared.windows.map { $0.tabbingMode = .disallowed }
                }
        }
        .commands {
            CommandGroup(replacing: .newItem) {} //remove "New Item"-menu entry
        }
    }
}

Unfortunately this did not work as NSApplication.shared.windows is empty in .onAppear.

My next step involved introducing an AppDelegate to my SwiftUI 2-lifecycle that implements applicationDidFinishLaunching(_:)...

class AppDelegate: NSObject, NSApplicationDelegate {

    func applicationDidFinishLaunching(_ notification: Notification) {
        print("Info from `applicationDidFinishLaunching(_:): Finished launching…")
        let _ = NSApplication.shared.windows.map { $0.tabbingMode = .disallowed }
    }
}

...and introducing this AppDelegate to the app:

import SwiftUI

@main
struct DQ_SyslogApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            CommandGroup(replacing: .newItem) {} //remove "New Item"-menu entry
        }
    }
}

This did the trick for me:

enter image description here

While I would prefer to have the menu entries removed entirely, this at least prevents the user from having tabs which is what the OP was asking for.

If anybody should happen to know a solution to hide those entries, please let us know. I couldn't find a CommandGroupPlacement that represents these menus...

Oriente answered 8/1, 2021 at 17:34 Comment(1)
See my answer for how to remove themCornet
L
23

Thanks to @JuJoDi

In case you are using SwiftUI life cycle (Scene), you could do the following:

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear {
                    NSWindow.allowsAutomaticWindowTabbing = false
                }
        }
    }
}
Lawley answered 24/7, 2021 at 0:37 Comment(0)
C
20

The equivalent in SwiftUI is the same thing as the equivalent in Swift (which is missed in that post for some reason). To completely remove these items from any windows for your application, in your app delegate you can set the boolean value allowsAutomaticWindowTabbing to false

func applicationWillFinishLaunching(_ notification: Notification) {
        NSWindow.allowsAutomaticWindowTabbing = false
}
Cornet answered 8/1, 2021 at 19:1 Comment(1)
Can I also remove the default Edit menu item this way?. I looked in the documentation for NSWindow, hoping to find a 'allowEdit' or similar.Askwith
O
7

I was looking for an answer for this as well and found out the following:

by default - as you already mentioned - the Show/Hide Tab is active:

enter image description here

There is a property on NSWindow called tabbingMode which allows us to take control by setting it to .disallowed. My problem though was: in a SwiftUI 2-lifecycle app, how can I get hold of the windows of the app?

Well, there's NSApplication.shared.windows, so my first (non working!!) attempt was to modify all the windows in my @main-App struct (as I already prevented new windows from being created, that should be suffice):

import SwiftUI

@main
struct DQ_SyslogApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear {
                    let _ = NSApplication.shared.windows.map { $0.tabbingMode = .disallowed }
                }
        }
        .commands {
            CommandGroup(replacing: .newItem) {} //remove "New Item"-menu entry
        }
    }
}

Unfortunately this did not work as NSApplication.shared.windows is empty in .onAppear.

My next step involved introducing an AppDelegate to my SwiftUI 2-lifecycle that implements applicationDidFinishLaunching(_:)...

class AppDelegate: NSObject, NSApplicationDelegate {

    func applicationDidFinishLaunching(_ notification: Notification) {
        print("Info from `applicationDidFinishLaunching(_:): Finished launching…")
        let _ = NSApplication.shared.windows.map { $0.tabbingMode = .disallowed }
    }
}

...and introducing this AppDelegate to the app:

import SwiftUI

@main
struct DQ_SyslogApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            CommandGroup(replacing: .newItem) {} //remove "New Item"-menu entry
        }
    }
}

This did the trick for me:

enter image description here

While I would prefer to have the menu entries removed entirely, this at least prevents the user from having tabs which is what the OP was asking for.

If anybody should happen to know a solution to hide those entries, please let us know. I couldn't find a CommandGroupPlacement that represents these menus...

Oriente answered 8/1, 2021 at 17:34 Comment(1)
See my answer for how to remove themCornet
A
4

I found how to remove the 'Edit' menu item after reading this article
https://steipete.com/posts/top-level-menu-visibility-in-swiftui/

func applicationDidFinishLaunching(_ notification: Notification) {
    NSWindow.allowsAutomaticWindowTabbing = false

    if let mainMenu = NSApp .mainMenu {
        DispatchQueue.main.async {
            if let edit = mainMenu.items.first(where: { $0.title == "Edit"}) {
                mainMenu.removeItem(edit);
            }
        }
    }

}
Askwith answered 13/6, 2021 at 13:29 Comment(1)
doesn't work =(Richmond
R
4

2023, swiftUI 2 and higher

Result in release build:

enter image description here

look at places with comments "HERE":

@main
struct MyFirstApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    init() {
        // HERE
        disallowTabbingMode()
    }
    
    @SceneBuilder
    var body: some Scene {
        WindowGroup {
            AppMainView()
        }
        .commands {
            //MenuLine_MyFirstApp_CustomAbout()

            // HERE
            MenuLine_File_NewWindow_Disable()
            
            //MenuLine_Help_SupportEmail()
        }
    }
}

And method + bonuses for understanding menus in SwiftUI

// Set Custom About window instead of default
fileprivate func MenuLine_MyFirstApp_CustomAbout() -> CommandGroup<Button<Text>> {
    CommandGroup(replacing: CommandGroupPlacement.appInfo) {
        Button("About") { appDelegate.showAboutWnd() }
    }
}

//Add some menu button into Help menu
fileprivate func MenuLine_Help_SupportEmail() -> CommandGroup<Button<Text>> {
    CommandGroup(after: CommandGroupPlacement.help) {
        Button("Support Email") { NSWorkspace.shared.open("mailto:[email protected]") }
    }
}

// Disable "File -> New window" menu item (make it absent in release build)
fileprivate func MenuLine_File_NewWindow_Disable() -> CommandGroup<EmptyView> {
    CommandGroup(replacing: .newItem) { }
}

fileprivate func disallowTabbingMode() {
    NSWindow.allowsAutomaticWindowTabbing = false
}



For those items that impossible to remove by way above you need to use sth like this:

(BUT! Important to understand that this way works only for english localisation)

extension AppDelegate {
    func applicationWillUpdate(_ notification: Notification) {
        DispatchQueue.main.async {
            guard let currentMainMenu = NSApplication.shared.mainMenu else { return }
            
            //Need to remove by index because of no ability to delete by name - localizations issue?
            
            ["Edit", "File", "Window"].forEach { name in
                currentMainMenu.item(withTitle: name).map { NSApp.mainMenu?.removeItem($0) }
            }
            
            guard let menu = currentMainMenu.item(withTitle: "View")?.submenu else { return }
            
            menu.removeAllItems()
            
            menu.addItem(self.recentsPlusFavorites)
            menu.addItem(self.recentsMini)
            menu.addItem(self.recentsLarger)
        }
    }
}
Richmond answered 30/8, 2021 at 23:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.