Get reference to current object - swift

My goal is to safe a reference from the button, label or textfield inside a variable.
The problem is that I don't know on which control the user tapped.
I am having a simple application which looks like this:
The user can touch any control.
It is easy enough with just those three controls because I can drag in a action. But if I am having many of them I can't handle them all over the action methods. Is there a general way in which I can safe a reference to the control in a variable so that I can know which of the controls is the active one?
Edit
As suggested I am using a function and assigning the variable to the sender of the function. This is how it looks in code:
var currentObject: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
myTextfield.action = #selector(myAction)
}
func myAction(sender: NSTextField)
{
print("aktuell: \(sender)")
currentObject = sender
}
As you can see this only works for a NSTextfield. Is there a way in which the function works for every control?

Set the tag attribute for each item, and then you can check sender.tag to identify which object is calling it.
To set the tag, select the Attributes inspector in Storyboard (upper right side - middle button of Utilities) and look for this section:

Related

how to hide button title but can still call it using sender.currentTitle?

I added a button in the main storyboard in Xcode. This button has an image as the background and its title "blueDoor" is shown on top of the background (see photo below).
There will be three buttons like this and they are linked to one IBAction. I would like to use sender.currentTitle to let the program know which button is clicked, but I don't want the text to show on the image.
How can I hide the text but still keep the title attribute so sender.currentTitle can be used? Or is there another way to do so?
A button with an image as the background:
You can use tag to do this.
Open storyboard > select your button > select Show the Attributes Inspector section in the right bar of the Xcode > scroll down and find Tag under the View section. And give different tags to your buttons.
Then your IBAction should be like this ->
#IBAction func didTapMyButtons(_ sender: UIButton) {
switch sender.tag:
case 0: // it is your first button's tag
// Do something with button which has tag `0`
case 1: // it is your second button's tag
// Do something with button which has tag `1`
case 2: // it is your third button's tag
// Do something with button which has tag `2`
default: break
}
Shortly: you can not do that. The currentTitle property is essentially a getter for button.titleLable.text. So is title(for: UIControl.State) setter (setTitle(String?, for: UIControl.State))
What docs say:
var currentTitle: String? { get }
Discussion
The value for this property is set automatically whenever
the button state changes. For states that do not have a custom title
string associated with them, this method returns the title that is
currently displayed, which is typically the one associated with the
normal state. The value may be nil.
This means whatever changes you bring to titleLabel.text, it is automatically reflected on a currentTitle property.
You can use tags as suggested or add a custom action handler for every button as you create them. E.g. for cancel and proceed buttons one will do it like so:
self.cancelButton.addTarget(self, action: #selector(self.cancelActionMethod), for: .touchUpInside)
self.proceedButton.addTarget(self, action: #selector(self.proceedActionMethod), for: .touchUpInside)
And then somewhere inside self:
#objc func cancelActionMethod() {
// some cancel specific actions
}
#objc func proceedActionMethod() {
// some cancel specific actions
}

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:
Setting window.delegate of the ChildViewControllers to NSApp.windows.first?.delegate
Setting the ChildViewController to becomeFirstResponder in its func viewWillAppear()
visualEffectView.addSubview(self.children[0].view, positioned: NSWindow.OrderingMode.above, relativeTo: nil)
Related problems/topics I found while researching:
Basic segmented control not working
Adding and Removing Child View Controllers
NSSegmentedControl - Odd appearance when placed in blur view
How to set first responder to NSTextView in Swift?
How to use #selector in Swift 2.2 for the first responder
Accessing methods, actions and/or outlets from other controllers with swift
How to use Child View Controllers in Swift 4.0 programmatically
Container View Controllers
issues with container view
Control a NSTabViewController from parent View
How to detect when NSTextField has the focus or is it`s content selected cocoa
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.
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.

How do I keep NSMenuItem selected in a NSPopover with NSMenu?

I have a NSPopUpButton with a built up NSMenu.
However, when an NSMenuItem is selected, it keeps going back to the first item.
In this example, I'd expect "Maged" to be selected.
Any ideas?
Related question/answer: https://stackoverflow.com/a/53387962/157656
Duplicate:
It's been suggested that this is a duplicate of the question
How do I change/modify the displayed title of an NSPopUpButton
"I would like an NSPopUpButton to display a different title than the title of the menu item that is selected."
However, my question was about getting the NSPopUpButton to show the selected item.
In the end, I changed how I did it.
I am using a NSButton to show the menu and NSTextField to display the results.
If anyone is interested in the details, here they are.
Build the menu up and use .representedObject to store whatever you need to access at the other end. I used a struct with the name and code the in it.
You need to assign the NSMenu to the NSButton.menu
Then have a click, something like this.
#IBAction func changeVoiceClicked(_ sender: NSButton)
{
if let event = NSApplication.shared.currentEvent {
NSMenu.popUpContextMenu(sender.menu!, with: event, for: sender)
}
}
Your NSMenuItem should have an action on it, which using a selector points to a function.
Something like this:
#objc func voiceChanged(sender: NSMenuItem)
{
// cope will nil
var voice : VoiceDetail = VoiceDetail();
if (sender.representedObject != nil) {
voice = sender.representedObject as! VoiceDetail;
}
// Do what you need to on menu select.
// update text field.
}

How to create toggle button that only toggles once

I want to create a button that, when pressed, toggles to a different format and stays that way, not allowing the user to toggle it back. For example, I am trying to create a button that says "Special 1" then when it is clicked by the user, it toggles to say "USED". When the word "USED" pops up on the UIButton, it stays that way and the user cannot change it back. Please help me out I can't figure this out. I am also open to other ideas to execute this other then a UIButton, for example maybe a UITableView??
What you can do is create a boolean (true or false) variable in your ViewController, and whenever the button is tapped you set this variable so that the tap function associated with the button can be manipulated.
1.) Make an action connection from your ViewController to your code this can be done by clicking the dual view button (the two circles in the top right corner) then holding control and drag your button into your code. :
For more information on connecting views to your code: https://codewithchris.com/9-hooking-it-all-up-swift-iboutlet-properties/
Skip to 6 min for the actual connection.
2.) Now we can just make our boolean variable, and make a basic if statement:
var didClick:Bool = false
#IBAction func touchButton(_ sender: UIButton) {
if !didClick { // If didClick is false
didClick = true // Set it to true
sender.setTitle("USED", for: .normal)
}
}
Note: The sender should be set to UIButton NOT Any
Should look like this:
_ sender: UIButton
If you are using a storyboard you can write this inside the IBAction function:
#IBAction func buttonName(_ sender: UIButton) {
sender.isUserInteractionEnabled = false
// Do whatever you need to do after...
}
sender's type could be UIButton or Any depending on how you made the connection from the storyboard.

Can I observe an optional value in swift? If not, how might I go about attempting to observe a change?

I'm trying to observe a change to the selection of an NSPopUpButton in Swift 4. In my view controller's viewDidLoad() I've set up the observation token to observe the selectedItem property of the NSPopUpButton
override func viewDidLoad() {
super.viewDidLoad()
observation = observe(\.myPopUpButton.selectedItem) {
objectToObserve, change in
if change.kind == NSKeyValueObservedChange.Kind.setting {
// code to execute goes here
}
}
I set a breakpoint on the line where observation is set to determine that the token is being configured with the correct key path. I also set a break inside the closure to see when it is executed. When I change the selection of the NSPopUpButton, the closure does not execute.
selectedItem is of type, NSMenuItem?, so my suspicion is that I can't set an observation on an optional property. But I can't find anything in Apple's documentation that states whether or not that is the case and I'm not sure how I would go about verifying it for myself.
So I have sort of a primary question along w/ some followups:
Can I observe an optional property in Swift 4.1?
If so, how can I troubleshoot this, what am I doing wrong?
If not, how can I go about trying to monitor the state of the NSPopUpButton?
Troubleshoots that I've already tried...
added #objc dynamic to the my myPopUpButton declaration
Many properties of many AppKit objects are not KVO-compliant. Unless the documentation specifically says the property is compliant, you should assume it's not compliant. NSPopUpButton's selectedItem property is non-compliant.
The easiest way to be notified that the pop-up button's selected item changed is to set the button's target and action:
override func viewDidLoad() {
super.viewDidLoad()
myPopUpButton.target = self
myPopUpButton.action = #selector(popUpButtonDidFire(_:))
}
#IBAction private func popUpButtonDidFire(_ sender: Any) {
// code to execute goes here
}
Note that if you're creating the pop-up button in a storyboard or xib, you can wire it to the popUpButtonDidFire method by control-dragging from the pop-up button to the view controller.
As mentioned in the comments in macOS Cocoa Bindings and Swift's property observers are a very powerful way to observe values, even in prior Swift versions. An outlet is not needed.
Create a property and use the didSet observer
#objc dynamic var selectedObject : MyObject? {
didSet {
}
}
In Interface Builder in Bindings Inspector bind Selected Object to the target controller Model Key Path > selectedObject.
MyObject is the type of the represented object of the menu item. If nothing is selected selectedObject is nil. You can bind also Selected Index, Selected Tag or Selected Value (but not simultaneously).