10

I am working on a project for removing maximize, minimize and close buttons which we can see in every window in every App/projects build with Xcode for macOS in SwiftUI-life cycle. How could I do this?


enter image description here


PS: Please consider I am coding on SwiftUI-life cycle/macOS and if your code or way is not compatible with that then I could not make use of it. thanks for help and your time.

Update, date 05-March-2022: The question is still open and looks for a SwiftUI approach. You can answer this question with SwiftUI api.

ios coder
  • 1
  • 4
  • 31
  • 91
  • 2
    Please use this sparingly. – Alexander Mar 31 '21 at 13:51
  • 1
    I'm asking to prevent a possible XY problem. Sometimes people ask these questions not because they want to hide these buttons for whatever reasons, but because they actually want to implement something that naturally does not have them. For example, a launch screen or a confirmation popup. In that case, they should have asked about how to implement that thing, not how to solve a problem that came up while attempting to replicate it. – Clashsoft Mar 31 '21 at 15:23
  • 1
    Ugly hack, `var body: some View { VStack {}.sheet(isPresented: .constant(true)) { Text("This is a window ").frame(width: 400, height: 400)}}` – Joakim Danielson Mar 31 '21 at 16:08

2 Answers2

9

Came across this question when looking to do similar myself so thought I'd add answer for posterity - or at least until Apple decides to make SwiftUI work nicely on macOS.

Anyway, the trick is to get hold of the window that holds the SwiftUI View and then tweak that. Easiest approach seems to be to listen for NSApplication's didBecomeActiveNotification and then grab a reference from NSApplication.shared mainWindow, keyWindow (or possibly filter it out by title from windows) as works best for the app in question.

Example of this using mainWindow for a helloWorld program...

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
            .frame(minWidth: 200, minHeight: 200)
            .navigationTitle("Undecorated")
            .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification), perform: { _ in
                NSApp.mainWindow?.standardWindowButton(.zoomButton)?.isHidden = true
                NSApp.mainWindow?.standardWindowButton(.closeButton)?.isHidden = true
                NSApp.mainWindow?.standardWindowButton(.miniaturizeButton)?.isHidden = true
            })
    }
}

Notes

  1. Although the buttons are gone from the window it is important to note that the menu entries and hotkeys for maximising, hiding and killing the window are still fully operational. That functionallity has to be removed separately from customising the window appearance.

  2. The alternative method I've seen to get hold of the window is to add an dummy "Find My Window" type View to the UI that uses AppKit to create it, and grabs a reference to the window when it does so. There is a nice exposition of this approach over on LostMoa's blog here.

  3. Would be very interested if anyone has found anything better.

shufflingb
  • 1,847
  • 16
  • 21
  • Thanks for your time, I know this way, my goal is pure SwiftUI. – ios coder Feb 22 '22 at 02:23
  • @swiftPunk - The question asks about "macOS in SwiftUI-life cycle" this works in that life-cycle. In terms of avoiding AppKit in SwiftUI, the recorded line from Apple's WWDC21 lounges in response to a question about opening new macOS windows is "We don’t have much API in this area" (see https://roblack.github.io/WWDC21Lounges/ ) - if that's not in place, then anything else UI more sophisticated that is macOS specific seems unlikely. Fwiw, this matches my experience so far, i.e. I don't believe that requested window customisation is currently possible to achieve without AppKit. – shufflingb Feb 22 '22 at 11:59
  • 1
    @swiftPunk - the confusion is being caused because SwiftUI App life cycle is the terminology Apple uses to describe the current SwiftUI approach to instantiating an app. It was introduced in 2020 as a replacement for the original AppDelegate approach, see for instance https://peterfriese.dev/posts/ultimate-guide-to-swiftui2-application-lifecycle/, https://medium.com/swlh/bye-bye-appdelegate-swiftui-app-life-cycle-58dde4a42d0f . fwiw agree about SwiftUI; just on macOS that future is unfortunately a little further away than on iOS at moment :-/ .. anyway, good luck it. – shufflingb Feb 22 '22 at 14:53
0

my two cents about using different approaches, integrating the previous nice solution from "shufflingb". Googling around and making some tests, I assemble another solution based on AppDelegate method. Hoping can help, I did prepare a project in gitHub with both approaches using a "const" (see below "let") to switch between approaches,

(https://github.com/ingconti/HideMacOsWindowButtons)

Here just some code for AppDelegate approach:

As a last side note, you cannot in any way to use it under Catalyst.

class MyAppDelegate: NSObject, NSApplicationDelegate {
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // 1st method:
        if USE_APP_DELEGATE{
            customize(window: NSApplication.shared.windows.first)
        }
    }
}

@main
struct HideMacOsWindowButtonsApp: App {
    
    @NSApplicationDelegateAdaptor private var appDelegate: MyAppDelegate

    var body: some Scene {
        WindowGroup {
           ContentView()
        }
    }
}

customise will do as per previous code:

//let USE_APP_DELEGATE = true
let USE_APP_DELEGATE = false

import AppKit


let ActiveNotif = NSApplication.didBecomeActiveNotification
typealias Window = NSWindow



fileprivate var count = 0

func customize(window: Window?) {

    count+=1
    
    print(USE_APP_DELEGATE ? "using App delegate" : "using notification", count)
    guard let window = window else{
        return
    }
    //window.titleVisibility = .hidden
    //window.titlebarAppearsTransparent = true
    window.isOpaque = false
    window.backgroundColor = NSColor.green

    window.standardWindowButton(.zoomButton)?.isHidden = true
    window.standardWindowButton(.closeButton)?.isHidden = true
    window.standardWindowButton(.miniaturizeButton)?.isHidden = true

}

I did add:

fileprivate var count = 0

just to verify no multiple calls (onlyy for debug).

ingconti
  • 10,876
  • 3
  • 61
  • 48