I have a (subclassed) NSCollectionView open, containing multiple text views. Each of the text views is mapped to a (subclassed) NSDocument object. (The idea is to use the document architecture's save functions but not its windowing functions, because I need multiple documents in the same window and the traditional document architecture doesn't allow that.)
Now, there's a function I'd like the user to be able to call from the main menu that will affect their currently selected document. That is: the document is currently visible in a text view with current focus, and the menu command should make an alteration to that document. But the sender of the menu command is just the menu. When the window controller handles the command from the menu, how can I tell it what the currently selected document is?
This is what the responder chain is for.
Since you're using NSCollectionView, you probably already have a subclass of NSCollectionViewItem. If not, create one. Implement your action method in this subclass. Example:
class DocumentItem: NSCollectionViewItem {
var document: MyDocument? {
return representedObject as? MyDocument
}
#IBAction func doThatThing(sender: AnyObject?) {
Swift.print("This is where I do that thing to \(document)")
}
// #IBOutlets and whatnot here...
}
You may need to set this as the custom class of your NSCollectionViewItem in your xib or storyboard.
Next, if your cell view (the view owned by your NSCollectionViewItem) isn't a custom subclass of NSView already, you should make it a custom subclass. You must override acceptsFirstResponder to return true:
class DocumentCellView: NSView {
override var acceptsFirstResponder: Bool { return true }
// #IBOutlets and whatnot here...
}
Make sure you set this as the custom class of your cell view in your storyboard or xib.
Finally, connect the action of your menu item to doThatThing: on First Responder:
Here's how it works:
Because the cell view now returns true for acceptsFirstResponder, when the user clicks a cell view in the collection view, the system will make it the first responder (the start of the responder chain).
When a view has a view controller, it makes that view controller the next responder after itself in the responder chain (if you are on OS X 10.10 Yosemite or later). Your cell view has a view controller: the item object you return from outlineView:itemForRepresentedObjectAtIndexPath:. (NSCollectionViewItem is a subclass of NSViewController, so your custom item is a view controller.)
When the user clicks the menu item, the menu item asks NSApplication to send its action along the responder chain, starting with the first responder. The first responder is the cell view, but it doesn't respond to the doThatThing: message. So NSApplication asks the view for its nextResponder, which is an instance of your NSCollectionViewItem subclass. That object does respond to doThatThing:, so NSApplication sends doThatThing: to your item object (with the NSMenuItem object as the sender argument) and doesn't check the rest of the responder chain.
Related
I have a toolbar on my macOS app, developed in Swift. The toolbarItem is dragable onto the NSWindowController, and I can setup an IABAction function, I just have a print in the function at the moment. And when I click on the button nothing happen the click does not seem to be recognised as an action ?
I had a few more line of code in the function but deleted it and now have just the print("test") line.
#IBAction func exportCsvClicked(_ sender: NSToolbarItem) {
print("test") }
No output observed, so I'd love to get "test" in the console when I click on this button.
Here is a list of the connections associated with the toolbarItem.
I found a way to get around the fact that the IBAction from an NSToolbarItem does not recognise the click on this item:
1/I made a customSegue from the item to the main window controller (it can go anywhere)
2/The prepare for segue function posts a notification to the notification saying that the item has been clicked.
3/The main view controller observes the notification and presents, either has a popup or a sheet (I got two buttons), the view that I have setup in the storyboard (referencing with the storyboardID). I found that you need to pass on all the necessary variable to setup the view from the main view Controller, and that there was issue of code in the viewDidLoad function of the sheet/popup view not running, I am suspecting that they might be different instances.
In my macOS application I have menu items, which are replicated also in main UI. Application is consisted of main window with its delegate and single view along with its view controller. In app delegate I capture menu item click action, then I need to send this event to my view controller in order to take appropriate actions and also update main UI.
Question is how to access my view controller (NSViewController) from app delegate?
If you have window as an IBOutlet you can do
var rootViewController: MyViewController? {
return window!.contentViewController as? MyViewController
}
func sendPressed(_ sender: Any?) {
rootViewController?.sendPressed()
}
If you don‘t have a window variable you can get it through
NSApplication.shared.orderedWindows.first!
Actually you don't need a reference to the controller. There is First Responder
Declare the IBActions
In Interface Builder connect the menu items to the First Responder (red cube) of the target object and choose the appropriate action.
The action will be executed in the first object of the responder chain which implements the action.
I have a custom UICollectionViewCell (let's call it MyCustomCell) that has a custom UITextField (let's call it MyCustomField) inside it that users can add text to. However, it seems no matter what I do, the tap inputs don't get from the MyCustomCell to its sub-view MyCustomField. Previously, I had this same arrangement, except MyCustomCell is a UITableViewCell, and that worked for some reason.
Right now I had to do a workaround, where whenever a cell is tapped, the cell sends a becomeFirstResponder() message to the textfield. However, this doesn't let the user tap on the "clear text" button inside the MyCustomField, nor does it let the user move the cursor around in the text field:
// function inside the MyCustomCell, called by the view controller
// whenever it thinks the user tapped on a cell:
func cellTapped()
{
// this is an instance of MyCustomField:
input.becomeFirstResponder();
}
Is there any way to "pass" the actual gesture to the MyCustomField (from either the MyCustomCell, or from the the view controller that manages them both)?
Update: this is what the view hierarchy looks like for the MyCustomCell (sub-class of UICollectionViewCell):
This may be of help to others who have similar issue: I believe #MultiColourPixel is correct in the comment above that there is a hidden "view" standing between the MyCustomField and MyCustomCell. What I did was:
Backed up the .xib and .swift files for the code for MyCustomCell and MyCustomField.
Delete the .xib and .swift files.
From the XCode menu, re-create the .swift file -- and make sure you check the "create XIB" as well when you create the .swift file / class that is subclass of the UICollectionViewCell.
Copy paste the old xib / swift contents into the new xib / swift.
It should work, without needing to explicitly send becomeFirstResponder() to the MyCustomField.
I have a Table View Cell and have added an action to it that I want to execute when a value is changed.
I have added the action method in the viewController
#IBAction func valueEntered(sender: NSTextFieldCell)
{
print("valueEntered")
}
I can edit the Table View Cell by double clicking on it, but my method is never called.
I know I have done this before but I must be missing a step.
You have to hook up the NSTableView delegate.
In your case, control-click and drag from Reseller Table up to File's Owner and choose delegate.
is it possible to deselect a UITextfield programmatically?
I have several textfields with a pickerView as input instead of the keyboard. But when I click on a cancel button I want to deselect the current textfield.
func resignFirstResponder() -> Bool
textField.resignFirstResponder()
Notifies the receiver that it has been asked to relinquish its status
as first responder in its window.
Discussion The default implementation returns YES, resigning first
responder status. Subclasses can override this method to update state
or perform some action such as unhighlighting the selection, or to
return NO, refusing to relinquish first responder status. If you
override this method, you must call super (the superclass
implementation) at some point in your code.
Availability Available in iOS 2.0 and later.
func becomeFirstResponder() -> Bool
textField.becomeFirstResponder()
Notifies the receiver that it is about to become first responder in
its window.
Return Value YES if the receiver accepts first-responder status or NO
if it refuses this status. The default implementation returns YES,
accepting first responder status.
Discussion Subclasses can override this method to update state or
perform some action such as highlighting the selection.
A responder object only becomes the first responder if the current
responder can resign first-responder status (canResignFirstResponder)
and the new responder can become first responder.
You may call this method to make a responder object such as a view the
first responder. However, you should only call it on that view if it
is part of a view hierarchy. If the view’s window property holds a
UIWindow object, it has been installed in a view hierarchy; if it
returns nil, the view is detached from any hierarchy.
Availability Available in iOS 2.0 and later.