Search code examples
swiftmacoscocoaactionnssegmentedcontrol

Swift macOS SegmentedControl Action not getting called


Description

I am trying to use NSSegmentedControls to transition between Child ViewControllers. The ParentViewController is located in Main.storyboard and the ChildViewControllers are located in Assistant.storyboard. Each ChildViewController has a SegmentedControl divided into 2 Segments and their primary use is to navigate between the ChildViewControllers. So they are set up as momentaryPushIn rather than selectOne. Each ChildViewController uses a Delegate to communicate with the ParentViewController.

So in the ParentViewController I added the ChildViewControllers as following:

/// The View of the ParentViewController configured as NSVisualEffectView
@IBOutlet var visualEffectView: NSVisualEffectView!

var assistantChilds: [NSViewController] {
    get { return [NSViewController]() }
    set(newValue) {
        for child in newValue { self.addChild(child) }
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    // Do view setup here.
    addAssistantViewControllersToChildrenArray()
}

override func viewWillAppear() {
    visualEffectView.addSubview(self.children[0].view)
    self.children[0].view.frame = self.view.bounds
}

private func addAssistantViewControllersToChildrenArray() -> Void {
    let storyboard = NSStoryboard.init(name: "Assistant", bundle: nil)
    let exampleChild = storyboard.instantiateController(withIdentifier: "ExampleChild") as! ExampleChildViewController
    let exampleSibling = storyboard.instantiateController(withIdentifier: "ExampleSibling") as! ExampleSiblingViewController

    exampleChild.navigationDelegate = self
    exampleSibling.navigationDelegate = self

    assistantChilds = [exampleChild, exampleSibling]
}

So far so good. The ExampleChildViewController has an NSTextField instance. While I am in the scope of the TextField, I can trigger the action of the SegmentedControls. Its navigating forward and backward as it should. But once I leave the scope of the TextField I can still click the Segments, but they are not triggering any action. They should be able to navigate forward and backward even if the TextField is not the current "First Responder" of the application. I think I am missing something out here, I hope anyone can help me with this. I know the problem is not the NSSegmentedControl because I am seeing the same behavior with an NSButton, which is configured as Switch/Checkbox, in the SiblingViewController. I just don't have any idea anymore what I am doing wrong.

It`s my first time asking a question myself here, so I hope the way I am doing is fine for making progress with the solution. Let me know if I can do something better/different or if I need to provide more information about something.

Thanks in advance!


Additional Information

For the sake of completeness:

The ParentViewController itself is embedded in a ContainerView, which is owned by the RootViewController. I can't imagine this does matter in any way, but this way we are not missing something out.


I am actually not showing the navigation action, because I want to keep it as simple as possible. Furthermore the action is not problem, it does what I want it to do. Correct me if I am wrong with this.


Possible solutions I found while researching, which did not work for me:


Related problems/topics I found while researching:


Solution

  • SOLUTION

    let parentViewControllerInstance = self.parent as! ParentViewController
    segmentedControl.target = parentViewControllerInstance
    

    In my case I just had to set the delegate as the target of the sendAction method.


    Background

    Ok, after hours of reading the AppKit Documentation I am now able to answer my own question.

    First, debugging the UI showed that the problem was definitely not in the ViewHierarchy.Debugging UI

    So I tried to think about the nature of NSButton and NSSegmentedControl. At some point I noticed that both are subclasses of NSControl.

    class NSSegmentedControl : NSControl
    class NSButton : NSControl
    

    The AppKit Documentation says:

    Discussion

    Buttons are a standard control used to initiate actions within your app. You can configure buttons with many different visual styles, but the behavior is the same. When clicked, a button calls the action method of its associated target object. (...) You use the action method to perform your app-specific tasks.

    The bold text points to the key of the solution – of its associated target object. Typically I define the action of an control item like this:

    button.action = #selector(someFunc(_:))
    

    This causes the NSControl instance to call this:

    func sendAction(_ action: Selector?, to target: Any?) -> Bool
    

    Parameter Description from the documentation:

    Parameters

    theAction

    The selector to invoke on the target. If the selector is NULL, no message is sent.

    theTarget

    The target object to receive the message. If the object is nil, the application searches the responder chain for an object capable of handling the message. For more information on dispatching actions, see the class description for NSActionCell.

    In conclusion the NSControl instance, which was firing the action method (in my case the NSSegmentedControl), had no target to send its action to. So it was only able to send its action method across the responder chain - which obviously has been nil while the first responder was located in another view.