Search code examples
swiftmacosnsbutton

NSButton's @objc action not being triggered in Swift for MacOS app


I have an NSButton in my ViewController class that is not triggering its click function (closeButtonPressed) when clicked.

I would test with a Button but UIKit isn't available for my project, as it's a non-Catalyst app for Mac.

As this has been a challenging and time-consuming issue, I am grateful for your time and expertise in helping me resolve it.

Minimal reproducible example

import SwiftUI

@main
struct MinimalReproducibleExampleApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

import Cocoa
import SwiftUI

struct ViewControllerWrapper: NSViewRepresentable {
    
    func makeNSView(context: Context) -> NSView {
        return ViewController().view
    }
    
    func updateNSView(_ nsView: NSView, context: Context) {
        // You can add any logic you need here to update the view as needed.
    }
}

class ViewController: NSViewController {
    
    let closeButton = NSButton(title: "X", target: nil, action: nil)
    
    override func loadView() {
        self.view = NSView(frame: NSRect(x: 0, y: 0, width: 300, height: 300))
        self.view.wantsLayer = true
        self.view.layer?.backgroundColor = NSColor.black.cgColor
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.wantsLayer = true
        view.layer?.backgroundColor = NSColor.cyan.cgColor
        
        closeButton.target = self
        closeButton.action = #selector(closeButtonPressed)
        closeButton.frame = CGRect(x: 100, y: 100, width: 50, height: 30)
        closeButton.bezelColor = NSColor.gray
        view.addSubview(closeButton)
    }
    
    @objc func closeButtonPressed() {
        print("Close button pressed")
    }
}


struct ContentView: View {
    
    private var viewControllerWrapper: ViewControllerWrapper
    
    init(){
        self.viewControllerWrapper = ViewControllerWrapper()
    }
        var body: some View {
        
            VStack {
                viewControllerWrapper
            }
        }
    }

(MacOS 12.6.3, Xcode 14.2)


Solution

  • As you are using a NSViewController you should use NSViewControllerRepresentable instead of using NSViewRepresentable.

    NSViewControllerRepresentable is explicitly for use with NSViewController while NSViewRepresentable is for use with NView.

    If you can change your ViewControllerWrapper to the following:

    struct ViewControllerWrapper: NSViewControllerRepresentable {
        func makeNSViewController(context: Context) -> ViewController {
            ViewController()
        }
    
        func updateNSViewController(_ nsViewController: ViewController, context: Context) {
    
        }
    }
    

    This should allow the button to work.