prepareForDragOperation() not called - swift

I've subclassed NSTextField (FileDropTextField), and created 2 of them in an NSView (in a window) in Main.storyboard.
When I drag a file from the desktop and drop it on the first FileDropTextField, it updates the field with the full path text, but prepareForDragOperation() is never called.
If I drag the same file (from the desktop) and drop it on the second FileDropTextField, the following 3 functions are called (as expected), and the field is not automatically updated with the path:
prepareForDragOperation()
performDragOperation()
concludeDragOperation()
The Swift code is as follows:
import Cocoa
class FileDropTextField: NSTextField {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code goes here.
}
override func awakeFromNib() {
NSLog("awakeFromNib")
self.register(forDraggedTypes: [NSPasteboardTypeString, NSURLPboardType, NSFilenamesPboardType])
}
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
self.stringValue = ""
NSLog("draggingEntered")
return NSDragOperation.generic;
}
override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
NSLog("draggingUpdated")
return NSDragOperation.generic;
}
override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
NSLog("prepareForDragOperation")
return true
}
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
NSLog("performDragOperation")
return true
}
override func concludeDragOperation(_ sender: NSDraggingInfo?) {
NSLog("concludeDragOperation")
}
override func draggingExited(_ sender: NSDraggingInfo?) {
NSLog("draggingExited")
}
override func draggingEnded(_ sender: NSDraggingInfo?) {
NSLog("draggingEnded")
}
}
It makes no difference if I create the second field in Main.storyboard via duplication, or if it is created (in Main.storyboard) as a separate operation. I've examined all the properties of each, and I see no difference between them (aside from object ID and constraint information).
Can anybody explain why I'm getting different behavior (when the file is dropped on the field) between these two objects when dropping the same object (desktop file) on each of them? The one that results in the call to prepareForDragOperation() is what I expect as correct behavior.
(I'm using Xcode 8.1, Swift3, on macOS Sierra 10.12.1)
Thanks!

have u tried to defocus on the first textfield, and drag drop again?
if it works, then i guess it because there's an invisible editor field is right on the top of textfield when it being focused, which makes it impossible to trigger delegate method.
so instead of using textfield, try to use textview

Related

Why doesn't performSegue() call shouldPerformSegue()

Quite new to programming in Swift and mobile development in general. I am trying to use performSegue() and control it without if-else statements. I made a google search how to use override func shouldPerformSegue() and tried to implement it in different ways but none of them solved my situation. Here I need to segue to Yellow or Green views if the switch is on using corresponding buttons. Even if I made return false, the function does not cancel the segue to happen. What is the reason of this behaviour and how can I fix it? Many thanks.
class ViewController: UIViewController {
#IBOutlet var segueSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func yellowButtonTapped(_ button: UIButton) {
performSegue(withIdentifier: "Yellow", sender: nil)
}
#IBAction func greenButtonTapped(_ button: UIButton) {
performSegue(withIdentifier: "Green", sender: nil)
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
// return segueSwitch.isOn
return false
}
}
shouldPerformSegue does not get called if you use performSegue(withIdentifier: in code. This function only get called if the segue is triggered from storyboard.
You can easily test this by adding 2 Buttons to a ViewController in storyboard one with the storyboard segue and one with an IBOutlet to a new ViewController. Then add a print statement to shouldPerformSegue. In the Button outlet call performSegue. Only the first button performing the segue from storyboard will print to the console.
And additionally you don´t need it. Any validation you would perform in shouldPerformSegue can be done in the ...ButtonTapped function:
#IBAction func yellowButtonTapped(_ button: UIButton) {
if validateSegue(){
performSegue(withIdentifier: "Yellow", sender: nil)
}
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
validateSegue()
}
func validateSegue() -> Bool{
......
}
This is deliberate and makes perfect sense. shouldPerformSegue lets you prevent an automatically triggered segue in case you don't want it performed. But if you didn't want a manual segue performed, all you had to do is nothing — don't say performSegue in the first place.
and control it without if-else statements
If-else is exactly how to control it.

Hiding Copy, Look Up, Share... in WKWebView UIMenuController

I am working on a PR for react-native-webview to add custom menu items to the RNCWebView (which is a subclass of WKWebView). It works in that it adds the additional options but "Copy | Look Up | Share..." always appear. Even if canPerformAction returns NO for every action (note that canPerformAction is not returning true/YES for copy)
I've come across so many other posts such as:
WKWebView and UIMenuController
Removing Copy, Look Up, and Share from UIMenuController
Custom Cut, Copy & Paste operations for WKWebView
And countless other ones that just suggest using CSS to hide the entire menu. I am not trying to hide the whole menu, but just "Copy, Look Up and Share" while letting my custom ones remain while using WKWebView.
My thought was that there was some other class up the responder chain that is still setting them to true, but I've tried extending and overriding as many as I know about and still those options show up:
extension UIView {
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
extension UITextView {
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
extension UIImageView{
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
extension UIScrollView{
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
extension UISlider{
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
extension UIWebView{
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
#objc private extension UIResponder {
func swizzle_canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
None of that works. Those three options still appear no matter what. Does anyone know what class could responding and adding these options? This seems like a common question but all the posts I find are either unanswered or wildly out of date.
// WKWebView subclass //
import WebKit
import UIKit
class MyWebView: WKWebView {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
// View controller //
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
// MARK: - Variables
var helloMenu = UIMenuItem()
// MARK: - IBOutlet
#IBOutlet weak var myWebView: MyWebView!
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
myWebView.navigationDelegate = self
helloMenu = UIMenuItem(title: "Hello", action: #selector(sayHello))
UIMenuController.shared.menuItems = [helloMenu]
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let urlStr = "https://www.google.com/"
if let url = URL(string: urlStr) {
let request = URLRequest(url: url)
myWebView.load(request)
}
}
// MARK: - Custom menu
#objc func sayHello() {
print("Hello")
}
}
First, sub-class WKWebView, removing all menu items.
Make an IBOutlet object of WKWebView and change its class name.
Add your custom menu item to your view controller.

How to detect which NSTextField is clicked in Cocoa?

I have two NSTextField objects which I want to highlight when user clicks on it.
The initial text field is already highlighted on NSWindow load. I am able to get mouse down event for text field click, but unable to distinguish which textfield did the user tapped.
I tried using hitTest on the text field using the NSPoint obtained from the NSEvent object, but the NSView returned is nil. The view it returns is that of the window's view and not that text field.
class SettingsViewController: NSViewController {
private var sview: SettingsView?
override func viewDidLoad() {
initEvents()
}
override func loadView() {
if let settingsView = SettingsView.createFromNib() {
self.view = settingsView
self.sview = settingsView as? SettingsView
}
}
func initEvents() {
self.sview!.emailTextField.delegate = self
}
}
extension SettingsViewController: NSTextFieldDelegate, NSTextDelegate {
override func mouseDown(with event: NSEvent) {
self.log.debug("mouse down: \(event.buttonNumber), \(event.eventNumber), \(event.locationInWindow)")
// How to know which text field triggered this?
}
func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
self.log.debug("control delegate")
return false
}
func textField(_ textField: NSTextField, textView: NSTextView, shouldSelectCandidateAt index: Int) -> Bool {
self.log.debug("text field should select")
return true
}
func textShouldBeginEditing(_ textObject: NSText) -> Bool {
self.log.debug("text field should being editing")
return true
}
}
class SettingsView: NSView {
private let log = Logger()
private static var topLevelObjects: NSArray?
#IBOutlet weak var emailTextField: ASTextField!
#IBOutlet weak var passwordTextField: NSSecureTextField!
// ...
}
I am adding delegate to only one text field.
self.sview!.emailTextField.delegate = self
But when I click on the passwordTextField, I am getting the mouse click event as well. Why is this happening?
How to distinguish NSTextField mouse click and highlight the text field?
I tried subclassing NSTextField and adding click handler, but it is not working.
class ASTextField: NSTextField {
private let log = Logger()
required init?(coder: NSCoder) {
super.init(coder: coder)
bootstrap()
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
bootstrap()
}
override func awakeFromNib() {
super.awakeFromNib()
bootstrap()
}
func bootstrap() {
self.delegate = self
}
}
extension ASTextField: NSTextFieldDelegate {
override func mouseDown(with event: NSEvent) {
// This is not working
self.log.debug("mouse down")
super.mouseDown(with: event)
}
}
If what you're looking for is to be able to select the text when you click (focus) the text field, you can override the class to simplify your task and you won't have to worry about locating the clicked field from the delegate.
For an NSView object, when it gets focus (ie. clicking or tabbing) it will call becomeFirstResponder so we can hook in there.
When an NSTextField becomes editable (or selectable) it grabs a reusable 'field editor' and overlays it on top of your text field during the editing. If your NSTextField has focus, you can grab this field editor using the currentEditor() call on the view.
So, once you have the field editor, you can perform selectAll on the editor to select the text.
Example class :-
class AutoselectOnFocusTextField: NSTextField {
override func becomeFirstResponder() -> Bool {
guard super.becomeFirstResponder() else {
return false
}
if let editor = self.currentEditor() {
editor.perform(#selector(selectAll(_:)), with: self, afterDelay: 0)
}
return true
}
}
Hope this helps!
I updated the ASTextField as below.
class ASTextField: NSTextField {
// ...
override func mouseDown(with event: NSEvent) {
self.sendAction(#selector(didClick(_:)), to: self)
super.mouseDown(with: event)
}
#objc func didClick(_ event: NSEvent) {
self.log.debug("did click")
}
}
In the SettingsView, I missed calling super.layout(), without which the click won't work, nor the other text field will get focus when clicked.
class SettingsView: NSView {
// ...
override func layout() {
self.log.debug("layout method")
super.layout() // This is important
}
}
NSTextField delegate methods are not required.
The method you overwrote, mouseDown(with), isn't a member of the NSTextFieldDelegate or NSTextDelegate protocols. You overwrote NSViewController.mouseDown(with).
Whenever that method is called, the thing that was clicked is your SettingsViewController's view.
To react to your textfield being selected, you use NSTextFieldDelegate .textField(_:textView:shouldSelectCandidateAt:), which you already have. The value of the textView parameter is the text view that was selected.

swift unhide buttons in one column if mouse is over row

I working with swift 4 for osx.
I have a view based NSTableView with 4 columns.
the cells in each column has got the same custom cell class:
class CustomCell: NSTableCellView {
#IBOutlet weak var btnInfo: NSButton!
private var trackingArea: NSTrackingArea!
override func awakeFromNib() {
super.awakeFromNib()
self.trackingArea = NSTrackingArea(
rect: bounds,
options: [.activeAlways, .mouseEnteredAndExited],
owner: self,
userInfo: nil
)
addTrackingArea(trackingArea)
}
override func mouseEntered(with event: NSEvent) {
super.mouseEntered(with: event)
btnInfo.isHidden = false
}
override func mouseExited(with event: NSEvent) {
super.mouseExited(with: event)
btnInfo.isHidden = true
}
}
Now i would like to realize the following situation:
if the user goes with the mouse over a row, the btnInfo should be visible and hide again, it the mouse leaves the row.
problem is (with the code above), that my apps crashes, because btnInfo will be nil
Logically: Because this button is only in column 4 available.
in all other columns it will be nil.
how can i solve this?
The solution is to add an NSTrackingArea to the entire view, not the individual cells. Then on the entire table view, you can get the mouse move events, take the NSEvent's locationInWindow. Then NSTableView has a method row(at point: NSPoint) -> Int that can get you the current row that should be highlighting the button.

How to catch some events on a NSControl in swift

I am writing an application for OSX in Swift and I am looking for a good way to catch events on a NSControl.
Obviously, I searched but the informations I found are often unclear or old.
In my case, I would like to catch several events on a NSTextField (key up, text changed, focus lost,...).
When I push on “Enter” in the NSTextField, it sends an action. Maybe is there a way to send an action when I click or write in the NSTextField?
You can subclass NSTextField and override textDidChange for text change, textDidEndEditing for lost focus and keyUp method for key up. Try like this:
import Cocoa
class CustomTextField: NSTextField {
override func viewWillMove(toSuperview newSuperview: NSView?) {
// customize your field here
frame = newSuperview?.frame.insetBy(dx: 50, dy: 50) ?? frame
}
override func textDidChange(_ notification: Notification) {
Swift.print("textDidChange")
}
override func textDidEndEditing(_ notification: Notification) {
Swift.print("textDidEndEditing")
}
override func keyUp(with event: NSEvent) {
Swift.print("keyUp")
}
}
View Controller sample Usage:
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let textField = CustomTextField()
view.addSubview(textField)
}
}
Sample