Preventing contextual menu showing on specific cell in a view based NSTableView - objective-c

Is there any way of preventing a contextual menu (and the associated selection "ring" around the cell view) being shown when right-clicking on a specific cell in a view-based NSTableView ?
I'm not talking about disabling the right-click action on ALL the cells, but only on specific ones.
I've obviously tried all the delegate methods dealing with selection changes but none works because the selectedRow property is not changing, only the clickedRow does.
So basically I'm looking for something equivalent to
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool
but for the clicked row not the selected row.
Note: the questions is about NSTableView on macOS and not the UITableViewon iOS.

I've found a way to do what I wanted, although looks like a little to involved for something that should be simpler. So I welcome any simpler solution.
It can be done by subclassing NSTableView :
class MyTableView : NSTableView {
override func menu(for event: NSEvent) -> NSMenu? {
let clickedPoint = self.convert(event.locationInWindow, from: nil)
let row = self.row(at: clickedPoint)
// no contextual menu for the last row
return row == self.numberOfRows - 1 ? nil : super.menu(for: event)
}
}
This example prevents the contextual menu to be shown for the last row, but a more generic solution could be implemented by adding a delegate with a method to return the menu for each cell.

Instead of subclassing NSTableView, a much easier approach is to set a menu delegate and remove all items within public func menuNeedsUpdate(_ menu: NSMenu) delegate method.
Example:
class MyViewController: NSViewController {
override func viewDidLoad() {
let menu = NSMenu()
menu.delegate = self
tableView.menu = menu
}
}
extension MyViewController: NSMenuDelegate {
public func menuNeedsUpdate(_ menu: NSMenu) {
//This will prevent menu from showing
menu.removeAllItems()
//Check if user has clicked on the cell or somewhere inside tableView
//area that is not populated with cells
guard tableView.clickedRow >= 0 else { return }
//Get model
let item = items[tableView.clickedRow]
//For cells that need context menu, add necessary menu items
if item.needsContextMenu {
menu.addItem(NSMenuItem(title: "Edit", action: #selector(tableViewEditItemClicked(_:)), keyEquivalent: "e"))
menu.addItem(NSMenuItem(title: "Delete", action: #selector(tableViewEditItemClicked(_:)), keyEquivalent: "d"))
}
}
}

Related

Swift 3: How can I show/hide the #IBAction button inside the ViewDidLoad() func?

I am trying to show/hide a button only if the information is available. For example, I get the information from the database, and if the field returns blank the a button should be hidden else show the button.
#IBAction func emailBtn(_ sender: AnyObject) {
//blank
}
viewdidload()
if emailURL.characters.count >= 5
{
emailBtn.isHidden = false //Giving error Please see the screenshot
}
else
{
emailBtn.isHidden = true // Giving error Please see the screenshot
}
}
Screenshot
You're trying to hide an #IBAction function. What you're intending to do is hide the button. What you need to do is create an outlet in your code that references that button.
In the Storyboard, Control+Drag the button to your class. Create an outlet. This will create something like the following:
#IBOutlet weak var myButton: UIButton!
You want to reference that button in viewDidLoad()
myButton.isHidden = true

inputAccessoryView Dismiss Keyboard

I have a textView docked to the bottom of the view. However, the keyboard wont dismiss when the user taps outside the commentTextView.
Current Attempt:
import UIKit
class CommentsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var commentBar: UIView!
#IBOutlet var commentTextField: UITextField!
override var inputAccessoryView: UIView {
return commentBar
}
override func canBecomeFirstResponder() -> Bool {
commentBar.removeFromSuperview()
return true
}
func textFieldShouldReturn(textField: UITextField!) -> Bool {
self.view.endEditing(true);
return false;
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
self.view.endEditing(true);
commentTextField.resignFirstResponder()
}
According to Apple's documentation, the keyboard won't dismiss by default when the user taps outside of the UITextView. You do need to handle this programmatically when you want to dismiss the keyboard by calling commentTextField.resignFirstResponder().
UITextFiled Reference about Managing the Keyboard
It is your application’s responsibility to dismiss the keyboard at the time of your choosing. You might dismiss the keyboard in response to a specific user action, such as the user tapping a particular button in your user interface. To dismiss the keyboard, send the resignFirstResponder message to the text view that is currently the first responder.
There are many ways for a user may hide the keyboard.
Situation 1: one is when the user tap on the Return button on the keyboard. This is exactly what your following function is for:
func textFieldShouldReturn(textField: UITextField!) -> Bool {
commentTextField.resignFirstResponder();
return true;
}
But the major problem is that the above function won't get called because you forget to set the UITextFieldDelegate. In short, you need to change the class definition to the following:
class CommentsViewController: UIViewController, UITextFieldDelegate /*Add This*/, UITableViewDelegate, UITableViewDataSource {
// ....
}
You will also have to set the delete either in the storyboard or by code. It is the same as setting the UITableViewDelegate and UITableViewDataSource. By the following is an example of setting it in code:
override func viewDidLoad() {
super.viewDidLoad()
commentTextField.delegate = self;
}
Situation 2: when user tap on the table view. You can simply implement the following UITableViewDelegate and it should work.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
commentTextField.resignFirstResponder();
}
Situation 3: when the user tap on a background view. This is what the following code is for. However, this function will not get called when user is tap on the table view. If you want to know why, please refer to the responder chain for more details.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
// self.view.endEditing(true); // This line is redundant.
commentTextField.resignFirstResponder()
}
To sum up, there is no quick and easy way to dismiss the keyboard say "when the user taps outside the textView". You do need to handle all different situations according to your need.
Try to set keyboard resign on your tableview as shown in figure.
check any
-dismiss on drag
-dismiss interactively
did you add Table View?
if yes, tableViewCell will receive the touch event instead of controller.view.
you could override the method in custom tableView cell:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
if (xxTextField.isFirstResponder){
return viewController.view
}
return self
}
Another way you can try,maybe it work:
add UITapGestureRecognizer to controller.view.
When you say "outside of my UITextField", I'm guessing you mean "on another element in my view". I can see that you have a UITableViewDelegate and UITableViewDataSource, which tells me you have a tableView inside CommentsViewController.
You can implement the function func tableView(_ tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath) and there you can resign the keyboard by calling commentTextField.resignFirstResponder()
First, your view controller needs to implement UITextFieldDelegate, so add that. Then, set your view controller as the delegate for the textfield
then add these methods. The first one will resignFirstResponder when your text field ends editing. The second resigns when you hit return. The last forces your textfield to end editing when you tap the background.
func textFieldDidEndEditing(textField: UITextField) {
textField.resignFirstResponder()
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
self.commentTextField.endEditing(true)
}
Modify
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
self.view.endEditing(true);
commentTextField.resignFirstResponder()
}
To
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
self.view.endEditing(true)
}
It should do the job.
Try this
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
self.view.endEditing(true);
commentTextField.resignFirstResponder()
}

Load data to NSTableView in swift

I am new to IOS/IOX development. I tried many sites to load data to my table on OSX swift using NSTableView. But all was failure.
The procedure i did was refering View-Based NSTableView in Swift - How to
Drag and droped a NSTableView, which was perfectly seen when I run the code.
Selected the first column and gave Identifier as "List" after setting number of columns to 1
In the appdelegate.swift I pasted as
class AppDelegate: NSObject, NSApplicationDelegate,NSTableViewDataSource,NSTableViewDelegate {
func applicationDidFinishLaunching(aNotification: NSNotification?) {
}
func applicationWillTerminate(aNotification: NSNotification?) {
}
func numberOfRowsInTableView(aTableView: NSTableView!) -> Int {
println("reached 1")
return 10
}
func tableView(tableView: NSTableView, viewForTableColumn: NSTableColumn, row: Int) -> NSView {
println("reached 2")
var cell = tableView.makeViewWithIdentifier("List", owner: self) as NSTableCellView
cell.textField.stringValue = "Hey, this is a cell"
return cell
}
}
Then i tried to Set the delegate and datasource as the Appdelegate for the table view by selecting table and pointing to "App Delegate" in "Application Scene". But it was not linking
When i run the program , table with no cells was loaded
Any help is greatly appreciated.
The easiest way would be selecting your table view in the XIB file and then in the Utilities panel on the right chose the third icon from the right (the arrow pointing right). Also make sure you show the document outline by clicking the icon in the lower left of the Interface Builder window. From the Utilities panel you can just drag from the delegate and datasource circle to your AppDelegate in the document outline.
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView?
{
if tableView.identifier=="tableViewIdentifier" {
let cellView = tableView.makeViewWithIdentifier("List", owner: self) as! NSTableCellView
cellView.textField?.stringValue = "Hey, this is a cell"
return cellView
}
return nil
}
set identifier to tableview of dropped table also set identifier to table column, set the delegate and datasource to the view controller in which the table is contained.
In your case, when using storyboards, you should make your view controller a data source and delegate for your table view, because they're contained in one scene. The class itself is called ViewController in Xcode project templates.
So, just move the code from application delegate to view controller and connect all the things in storyboard.

Disable navigation bar's back button

I'm trying to disable a navigation bar back button item (turning to gray and does not respond to touches).
I tried the following :
[self.navigationItem.backBarButtonItem setEnabled:NO];
Which does not work. Disabling the right button item works as a charm.
Surprisingly enough I could not find a similar question on SO. The closest one was about hiding the button (which works btw) but this is not so elegant (or adding a label to cover the button and prevent touches which keeps the same color of the back button --> also not so elegant:/).
I should mention that the view controller is a table view controller which is pushed by another navigation controller. (i.e. the back button is added automatically and not via IB or programmaticaly)
Any ideas ?
You can hide using
[self.navigationItem setHidesBackButton:YES animated:YES];
You do need to create a custom back button in order to disable it. Here's a simple example (No need to hide the backButtonItem):
Note that you'll probably want to disable the back swipe gesture (see here: How to disable back swipe gesture in UINavigationController on iOS 7 )
class MyClass: UIViewController {
private var backButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
backButton = UIBarButtonItem(title: "Back", style: .Plain, target: self, action: "goBack")
navigationItem.leftBarButtonItem = backButton
}
func goBack() {
navigationController?.popViewControllerAnimated(true)
}
func toggleBackButton() {
backButton.enabled = (backButton.enabled == false) ? true : false
}
}

How do I deselect all table rows in NSOutlineView when clicking in the empty space of the view?

For example when I click on the red dot below:
I want the following deselection to occur:
I set up the view-based NSOutlineView using bindings for both the data source and the selection indexes. So far i've tried to override the TableCellView becomeFirstResponder and also override NSOutlineView's becomeFirstResponder however it seems NSOutlineView never actually gives up first responder status?
Some advice would be very much appreciated!
I found this post on the topic. The solution appears to be in creating a subclass of NSOutlineView and overriding mouseDown: so that you can determine whether the click was on a row or not. When the click is on a row you just dispatch to super. If it's not you send deselectAll: to your NSOutlineView.
I haven't tried it myself but there are various posts around which come up with comparable code.
Use setAction: method of NSOutlineView.
[mOutlineView setAction:#selector(doClick:)];
[mOutlineView setTarget:self];
-(IBAction) doClick:(id)sender;
{
if ([mOutlineView clickedRow] == -1) {
[mOutlineView deselectAll:nil];
}
}
Swift 5. in NSOutlineViewDelegate
func outlineViewSelectionDidChange(_ notification: Notification) {
//1
guard let outlineView = notification.object as? NSOutlineView else {
return
}
outlineView.deselectAll(nil)
}