As the title mentions. How can I forbid the copy (Command + C) and paste (Command + V) operations in an NSTextField or NSSecureTextField?
Create a subclass of UITextField. In that subclass, implement
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if (sel_isEqual(action, #selector(copy:))) {
return NO;
}
return [super canPerformAction:action withSender:sender];
}
Related
This is the error picture:
My class is a subclass of NSTextView, it supports dragging. but When I drag text to former location, there will be a sticked inserting point.
Then I click elsewhere ,the normal insertion point appears at the end of text(which is correct),
but the first point did not disappear automatically, even though I delete the whole string.
there are only 3 method or property in NSTextView related to insertion point.
#property (readonly) BOOL shouldDrawInsertionPoint;
#property (copy) NSColor *insertionPointColor;
- (void)updateInsertionPointStateAndRestartTimer:(BOOL)restartFlag;
- (void)drawInsertionPointInRect:(NSRect)rect color:(NSColor *)color turnedOn:(BOOL)flag;
The first one is readonly, I tried the second one ,I set white color, when dragging and original color after dragActionEnded. I did not work.
Waiting for your resolution.
Thanks!
The following is the drag delegate code I wrote.
#pragma mark - Destination Operations
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
/------------------------------------------------------
method called whenever a drag enters our drop zone
--------------------------------------------------------/
// Check if the pasteboard contains image data and source/user wants it copied
if ([sender draggingSourceOperationMask] & NSDragOperationCopy )
{
//accept data as a copy operation
return NSDragOperationCopy;
}
return NSDragOperationNone;
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
//check to see if we can accept the data
NSURL *fileURL=[NSURL URLFromPasteboard: [sender draggingPasteboard]];
if (fileURL == nil) {
return NO;}
NSString *filePathAndName = [[NSString alloc] initWithUTF8String:[fileURL fileSystemRepresentation]];
if (filePathAndName == nil)
{
return NO;
}
NSString *fileExtension = [[filePathAndName pathExtension] uppercaseString];
if (fileExtension == nil)
{
return NO;
}
if ([fileExtension isEqualToString:#"JPG"] ||
[fileExtension isEqualToString:#"JPEG"] ||
[fileExtension isEqualToString:#"PNG"] ||
[fileExtension isEqualToString:#"GIF"] ||
[fileExtension isEqualToString:#"BMP"])
{
return YES;
}
else
{
return NO;
}
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender{
if ( [sender draggingSource] != self )
{
if ( [[[sender draggingPasteboard] types] containsObject:NSFilenamesPboardType] ) {
NSURL* fileURL=[NSURL URLFromPasteboard: [sender draggingPasteboard]];
NSArray *files = [[sender draggingPasteboard] propertyListForType:NSFilenamesPboardType];
[[NSNotificationCenter defaultCenter] postNotificationName:ZayhuSendPicToPeer object:self userInfo:#{#"filesArray":files}];
}
}
return YES;
}
- (NSString *)preferredPasteboardTypeFromArray:(NSArray *)availableTypes restrictedToTypesFromArray:(NSArray *)allowedTypes{
if ([availableTypes containsObject:NSPasteboardTypeString])
{
return NSPasteboardTypeString;
}
return [super preferredPasteboardTypeFromArray:availableTypes restrictedToTypesFromArray:allowedTypes];
}
I have encountered your problem.
My way to solve this problem is not subclassing NSTextView to do all drag and drop tasks but put NSTextView on a custom view and let the custom view do the main part of the job.
If i subclassed NSTextView to be the drag destination, i encountered some other UI problems such as disappearing insertion point when the textview is backed by a CALayer.
The following code are written in Swift, but it stills convey the idea for the solution.
First of all, for text drag & drop. You can subclass NSTextView and just override acceptableDragTypes
override var acceptableDragTypes : [String] {
return [NSStringPboardType]
}
For other types of drag & drop, let the custom view deal with them. Take dragging files as example,
class MyCustomView: NSView {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
registerForDraggedTypes([NSFilenamesPboardType])
}
override func draggingEntered(sender: NSDraggingInfo) -> NSDragOperation {
if sender.draggingSource() === self {
return .None
}
return sender.draggingSourceOperationMask()
}
override func prepareForDragOperation(sender: NSDraggingInfo) -> Bool {
//add your custom logic here
return true
}
override func performDragOperation(sender: NSDraggingInfo) -> Bool {
//add your custom logic here
return true
}
}
I had need of subclassing a NSTextView for some other reasons and needed to add some custom object dragging. I ran into the same things you did with the insertion point weirdness.
In my case, instead of adding another view to handle the custom object logic I did override the performDragOperation and just called the super in the case that it wasn't my specific type. This seemed to work perfectly.
This allowed the default handling the String case in its natural way and the insertion UI stuff all cleared up.
This seems to be one of the most frequently discussed topics here but I couldn't find a solution which actually works. I'm posting this question to share a solution which I found as well as hoping to find a better/cleaner solution
Description of situation:
There is a UIWebview in my application
There is text input/area in the webview
Long pressing on the text area/input brings up a context menu with 'cut', 'copy', 'define' etc.
We need to disable this menu without disabling user input.
What I've tried so far
(Stuff that doesn't work) :
Override canPerformAction
This solution tells us to add canPerformAction:withSender: to either subclass of UIWebview or in a delegate of UIWebview.
- (BOOL) canPerformAction:(SEL)action withSender:(id)sender
{
if (action == #selector(defineSelection:))
{
return NO;
}
else if (action == #selector(translateSelection:))
{
return NO;
}
else if (action == #selector(copy:))
{
return NO;
}
return [super canPerformAction:action withSender:sender];
}
Does not work because the canPerformAction: in this class is does not get called for menu items displayed.
Since the sharedMenuController interacts with the first responder in the Responder chain, implementing canPerformAction in the container skipped select and selectAll because they had already been handled by a child menu.
Manipulating CSS
Add the following to CSS:
html {
-webkit-user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color:rgba(0,0,0,0);
}
This does work on images and hyperlinks but not on inputs.
:(
The root cause of the first solution not working is the subview called UIWebBrowserView. This seems to be the view whose canPerformAction returns true for any action displayed in the context menu.
Since this UIWebBrowserView is a private class we shouldn't try to subclass it (because it will get your app rejected).
So what we do instead is we make another method called mightPerformAction:withSender:, like so-
- (BOOL)mightPerformAction:(SEL)action withSender:(id)sender {
NSLog(#"******Action!! %#******",NSStringFromSelector(action));
if (action == #selector(copy:))
{
NSLog(#"Copy Selector");
return NO;
}
else if (action == #selector(cut:))
{
NSLog(#"cut Selector");
return NO;
}
else if (action == NSSelectorFromString(#"_define:"))
{
NSLog(#"define Selector");
return NO;
}
else if (action == #selector(paste:))
{
NSLog(#"paste Selector");
return NO;
}
else
{
return [super canPerformAction:action withSender:sender];
}
}
and add another method to replace canPerformAction:withSender: with mightPerformAction:withSender:
- (void) replaceUIWebBrowserView: (UIView *)view
{
//Iterate through subviews recursively looking for UIWebBrowserView
for (UIView *sub in view.subviews) {
[self replaceUIWebBrowserView:sub];
if ([NSStringFromClass([sub class]) isEqualToString:#"UIWebBrowserView"]) {
Class class = sub.class;
SEL originalSelector = #selector(canPerformAction:withSender:);
SEL swizzledSelector = #selector(mightPerformAction:withSender:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(self.class, swizzledSelector);
//add the method mightPerformAction:withSender: to UIWebBrowserView
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
//replace canPerformAction:withSender: with mightPerformAction:withSender:
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
}
}
And finally call it in the viewDidLoad of the ViewController:
[self replaceUIWebBrowserView:self.webView];
Note: Add #import <objc/runtime.h> to your viewController then error(Method) will not shown.
Note: I am using NSSelectorFromString method to avoid detection of private API selectors during the review process.
Also you can hide menu:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(menuWillBeShown:) name:UIMenuControllerWillShowMenuNotification object:nil];
...
- (void)menuWillBeShown:(NSNotification *)notification {
dispatch_async(dispatch_get_main_queue(),^{
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO];
});
}
The essential trick here is dispatch_async.
Is there anyway, I can copy the text inside UITextView to the clipboard when the user chooses Select All without waiting for Copy command from the user?
In other words, is it possible to make a -selectAll method like the following?
-(BOOL)selectAll
{
//custom code to copy the text
return YES;
}
You can subclass UITextView and override canPerformAction where you can call your own clipboard function if selectAll is chosen
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (action == #selector(selectAll:))
{
[UIPasteboard generalPasteboard].string = self.text;
return YES;
}
return [super canPerformAction:action withSender:sender];
}
In my NSTableView subclass MyTableView I've overwritten
- (void) keyDown:(NSEvent *)event {
if ( [event keyCode] == 51 || [event keyCode] == 117 ) {
[super keyDown:event];
return;
}
}
51 is the code for the delete button. I'm expecting the table view to delete the selected item as before the subclassing.
The event is correctly caught and the keyDown method of the superclass is invoked. However, the item is not deleted anymore. Why ?
Thanks
Recommend you override keyDown: in your window class.
-(void) keyDown: (NSEvent *) event
{
NSString *chars = [event characters];
unichar character = [chars characterAtIndex: 0];
if (character == NSDeleteCharacter || character == NSBackspaceCharacter)
{
NSTableView* view = (NSTableView*)[self firstResponder];
if(view == theTableView)
{
// do something to delete the item from your data model and reload the tableview
}
}
}
If you are trying just to invoke a specific method when the Delete key is pressed, I'd suggest overriding the -deleteBackward: method (part of NSResponder), because it isolates this interception more specifically. It also manages the problem of remapped keyboards, macros, etc.
There's also -deleteForward for the delete key instead of the backspace key.
-(void)deleteBackward:(id)sender
{
// do my override here
// do this only if super implements deleteBackward:
[super deleteBackward: sender]
}
I have an iPad application which has a sign up form within it. The form is very basic and contains only two UITextFields which are for Name & Email address.
The first TextField is for the candidates Name, When they enter their name in and press 'Next' on the keyboard I want this to automatically move to the next Email Address TextField to editing.
Any idea how I can set the next button the keyboard to jump to the next keyboard?
Thanks
You need to make your view controller the UITextField delegate, and implement the UITextField delegate method:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if (textField == nameField) {
[textField resignFirstResponder];
[emailField becomeFirstResponder];
} else if (textField == emailField) {
// here you can define what happens
// when user presses return on the email field
}
return YES;
}
Swift version:
func textFieldShouldReturn(textField: UITextField) -> Bool {
if textField == nameField {
textField.resignFirstResponder()
emailField.becomeFirstResponder()
} else if textField == emailField {
// here you can define what happens
// when user presses return on the email field
}
return true
}
You may also want to scroll your view for the emailField to become visible. If your view controller is an instance of UITableViewController, this should happen automatically. If not, you should read this Apple document, especially Moving Content That Is Located Under the Keyboard part.
Additionally to #lawicko 's answer I often change the button text to give that final finishing touch (e.g. says next when there are more fields and then done when on the last):
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
BOOL isLastTextField = //.. your logic to figure out if the current text field is the last
if (isLastTextField) {
textField.returnKeyType = UIReturnKeyDone;
} else {
textField.returnKeyType = UIReturnKeyNext;
}
}
Swift version of correct answer.
In my experience, you do not need to resignFirstResponder when switching textFields.
In this example, it's just your basic username and password textFields.
The keyboard "return key" in storyboard for username is set to "Next" and the one for password is set to "Done".
Then just connect the delegates for these two text fields and add this extension and you're pretty much done.
extension LoginViewController: UITextFieldDelegate {
func textFieldShouldReturn(textField: UITextField) -> Bool {
if textField == textFieldPassword {
self.view.endEditing(true)
} else {
textFieldPassword.becomeFirstResponder()
}
return true
}
}
A more consistent and robust way is to use NextResponderTextField
You can configure it totally from interface builder.
All you need to do is
Set the class type of your UITextField to be NextResponderTextField
Then set the outlet of the nextResponderField to point to the next responder it can be anything UITextField or any UIResponder subclass. It can be also a UIButton and the library is smart enough to trigger the TouchUpInside event of the button only if it's enabled.
Here is the library in action:
A Swift 4 extension. Just pass the array of UITextFields and it will connect each one to the next until the last one which resigns the first responder (hides the keyboard):
extension UITextField {
class func connectFields(fields: [UITextField]) {
guard let last = fields.last else { return }
// To reset the targets in case you call this method again to change the connected fields
fields.forEach { $0.removeTarget(nil, action: nil, for: .editingDidEndOnExit) }
for i in 0 ..< fields.count - 1 {
fields[i].returnKeyType = .next
fields[i].addTarget(fields[i + 1], action: #selector(UIResponder.becomeFirstResponder), for: .editingDidEndOnExit)
}
last.returnKeyType = .continue
last.addTarget(last, action: #selector(UIResponder.resignFirstResponder), for: .editingDidEndOnExit)
}
}
- (BOOL) textFieldShouldReturn:(UITextField *)textField
{
if (textField == self.textFieldName)
{
[self.textFieldName resignFirstResponder];
[self.textFieldPassword becomeFirstResponder];
}
else if (textField == self.textFieldPassword)
{
[self.textFieldPassword resignFirstResponder];
[self login:self];
}
return true;
}
#interface MLLoginViewController ()<UITextFieldDelegate>
#end
#implementation MLLoginViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.textFieldName.delegate = self;
self.textFieldPassword.delegate = self;
Make an outlet for the textfield, then
viewController.h
(IBAction)textFieldDoneEditing:(id)sender;
viewController.m
(IBAction)textFieldDoneEditing:(id)sender {
[textField resignFirstResponder];
if (textField == nameField) {
[emailField becomeFirstResponder];
}
}
Make the relation between (show the connections inspector > Sent Events)didEndOnExit and textFieldDoneEditing