I am trying to access the copy, cut, and paste methods of a NSTextField instance in its window delegate so I can customize these methods. I find that unlike tableViews and textViews, the textfield's copy, paste and cut actions are not responsive in the delegate. My understanding is that all text controls share the window's field editor yet this does not seem to be the case.
I thought perhaps the TextField's field editor was not being shared with the window delegate, however I did some testing I see that as I am typing in control, those field editors are identical--very strange.
My current work-around is to use a subclass instance of NSTextView where the copy and paste action methods respond as needed. This, however, has its own issues and I was hoping there was some way to get NSTextFields to work as expected.
A nstextfield does not have copy and paste functions. Those are only found in nstextview. the catch is that when a textfield is edited it opens up a textview called a fieldeditor during the editing and sets that as the first responder.
How to solve:
Each text field has a cell as a child connected to it (called cell in the picture but should be named more appropriately, e.g. CustomTextEditor):
The cell has a method for implementing a custom field editor called fieldEditorForView:
class cell: NSTextFieldCell {
var editor: NSTextView
override func fieldEditorForView(aControlView: NSView) -> NSTextView? {
if editor == nil {
editor = ESPasteView()
}
return editor
}
}
This above function allows you to provide your own custom NSTextView subclass:
class ESPasteView: NSTextView, NSTextViewDelegate {
override func paste(sender: AnyObject?) {
Swift.print("user tries to paste")
super.pasteAsPlainText(nil)
}
}
Credit to:
How to disable context menus with right mouse click in an NSTextField (Cocoa)?
and Ken Thomases who pointed out the field editor.
Maybe you could take a look at NSTextField's:
- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard type:(NSString *)type;
- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard type:(NSString *)type;
This would allow you to intercept the call customize the response.
Related
I'm attempting to figure out how to introduce a second column to Material's Menu, but am finding controlling two using the same View Controller's menu delegate methods tricky. Might there be a trivial way to accomplish this that I haven't thought of? I could probably make another View Controller be a second Menu's delegate, but this doesn't seem an elegant solution.
Cheers!
If you are using the Menu object itself, and not the MenuController, you should be able to do a compare in the delegation method, where you compare the menu being called with your view controller declaration property for either Menu. For example:
extension ViewController: MenuDelegate {
func menu(menu: Menu, tappedAt point: CGPoint, isOutside: Bool) {
if menu == menuA {
} else if menu == menuB {
}
}
}
Now you should only need to do this if the functionality differs for each columns menu. If you need any further help, please share you code :)
I'm given an arbitrary NSAttributedString (parsed from markdown, not that it matters here) which may contain URLs that I want to be clickable in a text field within an NSTableView cell. The requirements state that if the user clicks the URL, they be taken to it with the default browser. IF they click anywhere else in the cell, we have default behavior (displaying an additional info popup).
I'm attempting to use a NSTextView to display the content. However, clicking outside the URL but within the view selects the text and eats the mouse click. Making the view not selectable won't allow clicking the URL either. I also don't really want the text to be selectable but that's a minor side problem.
So... I decided to make my view controller an NSTextViewDelegate so I could use some of those callbacks. But my app crashes if I set the NSTextView's delegate property to 'self'. This happens even if I don't implement any of the functions, even though they are all optional.
I'm using Swift 3 and wonder if there's some bug or other issue there? The call stack appears to be sending a textView:willChangeSelectionFromCharacterRanges:toCharacterRanges: message even though it's not implemented. And incidentally, implementing that method isn't helping either.
Any help, or sample code in Swift 3 using the delegate protocol, would be greatly appreciated.
Here's the crash I get by simply setting the delegate property on NSTextView:
By request, here's the code that set's the delegate. Currently I just set it whenever the message changes. This can obviously be optimized but for now I just want to see it work.
var notification: SSNotification! {
didSet {
guard let notificationCellView = self.view as? SSNotificationCellView else { return }
notificationCellView.subjectLabel.stringValue = notification.subject
if let description = notification.message , description != "" {
let attrString = TSMarkdownParser.standard().attributedString(fromMarkdown: description)
notificationCellView.messageLabel.textStorage?.setAttributedString(attrString)
notificationCellView.messageLabel.isHidden = false
notificationCellView.messageLabel.delegate = self
} else {
notificationCellView.messageLabel.isHidden = true
}
}
}
I never did figure out why I was crashing but I was able to come up with a workaround. I was originally trying to make the view controller for the table cell which contained NSTextView be the delegate. I changed it so that the cell's view subclass itself was the delegate and all is well.
I don't get it but it works, so that's what matters.
I want to display a 'NSPanel' for the user to input a name for a new folder. Why a NSPanel? Because it looks awesome! It hosts one TextField and one PushButton to confirm the name. It shall also close the window when clicked.
It displays when the "add" button gets clicked in my menu. It also closes when the "done" button gets clicked in the NSPanel. But when I click "add" again it doesn't show up anymore. That also occurs when I close it via the normal "close button" in the title bar. So it is not explicitly related to the "done"-PushButton. I also tested implementing func windowWillClose(notification: NSNotification) which also doesn't get triggered in either cases. What could be the problem? Also, does it somehow need to be a "new" window every time? Or am I using this correctly for user input? I mean it just gets instantiated once and then "shown" and "unshown" or am I wrong?
So I did a new Cocoa-Class - Subclass of NSWindowController - and let xCode create a .xib for that also. In that .xib I "designed" the NSPanel. I ticked visible at launch without that the window wouldn't appear when the menu button gets clicked. I also hooked up an IBOutlet for the NSPanelin my Cocoa Class. My Class at the moment looks like this:
import Cocoa
class NamingHUD: NSWindowController, NSWindowDelegate {
#IBOutlet var insertNameWindow: NSPanel!
#IBOutlet weak var nameTextField: NSTextField!
override var windowNibName : String! {
return "NamingHUD"
}
override func windowDidLoad() {
super.windowDidLoad()
insertNameWindow.center()
insertNameWindow.makeKeyAndOrderFront(nil)
NSApp.activateIgnoringOtherApps(true)
}
#IBAction func userSetName(sender: NSButton) {
print("Close button clicked")
insertNameWindow.close()
}
}
In my Main Class I declared it as a variable like this:
var namingHUD:NamingHUD!
and then in override func awakeFromNib() as:
namingHUD = NamingHUD()
as well as in a click handler like:
#IBAction func addClicked(sender: NSMenuItem) {
namingHUD.showWindow(nil)
}
Now. When I click and addClicked() gets called the window shows up as expected. Fine! I enter a name and hit the "done" button and it closes the window properly. Also Fine! But when I click again, say to add another folder, the window doesn't show up anymore. I also created a Preferences Window the exact same way. But with a Window instead of a NSPanel inside. That totally works as it should.
So I clearly confused something or forget something. What could it be? I openly admit that it is the first time I am working with any kind of window outside of following a tutorial. So I clearly didn't grasp the whole concept of it. I read up about windows in Apples Developer Guide and it kinda makes sense. But... well, doesn't work at the moment. Am I "misusing" the NSPanel? Shouldn't be the case as it inherits from NSWindow or?
Did you connect the window outlet of NamingHUD to your awesome panel? Nibs are loaded lazily:
namingHUD = NamingHUD() // init the controller but doesn't load the nib
...
namingHUD.showWindow(nil) // now you are loading it for the first time
It works the first time because showWindow() loads the nib and show the window referenced by the window outlet. Your panel shows up because it's set to "Visible at launch". Your of course had no window to show.
Subsequent clicks don't load the nib file again, only order the window outlet to show up. That's why your panel did not show again. FYI: an NSPanel is a subclass of NSWindow so it has everything that NSWindow has, and then some more.
I was experimenting with how a custom keyboard affects my app. I installed Swype on my iPhone 6.
I find that in some of my views where I have custom inputView property set on a text field, the Swype keyboard is overriding and presenting instead of my picker. This completely breaks my UI and cannot be allowed.
Is there a way to explicitly tell iOS 8 only to use the inputView I have set?
Is this a bug, perhaps? It is not at all expected behavior to allow a third party to override my input spec?
You can disable custom keyboard for your app with the following code:
include this in your app delegate:
- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(NSString *)extensionPointIdentifier {
if ([extensionPointIdentifier isEqualToString: UIApplicationKeyboardExtensionPointIdentifier]) {
return NO;
}
return YES;
}
Swift 4
func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplicationExtensionPointIdentifier) -> Bool {
if (extensionPointIdentifier == .keyboard) {
return false
}
return true
}
Using the answer from Pablo Ezequiel Romero as a starting point, I was able to get things to work for me. Essentially, rather than using a UIViewController for the custom keyboard, use a UIInputViewController and put your controls inside the UIInputViewController's inputView. Then, assign the inputView of your UITextField or UITextView to the inputView of the UIInputViewController.
If you're using auto layout, you need to make sure that you set everything properly and make sure to set an initial height constraint on the inputView and set its priority below the max 999 level (I used 800). Any height will do; the system will replace your constraint with one of its own. The lower priority avoids auto layout conflicts. (For me, if I didn't include this constraint, the final view wouldn't have any height at all.)
When I did all this, I was able to switch in and out of my (internal to the app) custom keyboard and any third-party keyboard extension.
I had a similar issue and I was able to fix it using UIInputViewController. Basically the view that I set in inputView is the view of my UIInputViewController subclass. I was using UIViewController, but after replacing the base view controller it started to work well.
To stop users from using custom keyboard extensions for individual fields, you can use SecureTextEntry in UIKit (SecureField in SwiftUI). Although these objects automatically mask user input without the option to change it (which makes them simple to use for passwords, not applicable for all kinds of secrets).
For example, the SwiftUI declaration looks as follows:
var passw: String = ""
(...)
SecureField("Password", text: $passw)
The first argument is a prompt that the user will see.
Note that SecureField will automatically block keyboard extension not only on its field but also on other fields on the same application screen (applies to TextField as well).
i need a way to deactivate the auto selection from a UITextView.
I deselected all the attributes from interface builder, but when i touch the text, the selection appear!
Have a solution? A magick tricks?
thanks.
You need to create a subclass of UITextView and override the canPerformAction method.
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (action == #selector(copy:)
return NO;
return [super canPerformAction:action withSender:sender];
}
The selector values you can expect from canPerformAction can be found in the UIResponderStandardEditActions Protocol Reference
The UIResponder Class Reference will help as well.
canPerformAction:withSender:
This default implementation of this
method returns YES if the responder
class implements the requested action
and calls the next responder if it
does not. Subclasses may override this
method to enable menu commands based
on the current state; for example, you
would enable the Copy command if there
is a selection or disable the Paste
command if the pasteboard did not
contain data with the correct
pasteboard representation type. If no
responder in the responder chain
returns YES, the menu command is
disabled.
Look at UIResponder Class Reference
So create a subclass of UITextView that overrides the canPerformAction:withSender: method and return 'NO' for every action that you don't want to perform on textview.