I think I've done my homework on this, but haven't found what I'm looking for.
I'm developing an iPad app and I'd like to be able to:
Watch for keypress events (if there are such things) and respond to them by:
altering what was entered and have the altered results appear in the UITextField (instead of what the user entered). This could be a macro, for example.
in some cases, I'd like to programmatically hit the shift button, so the next work the user enters begins with a capital letter.
I have seen in similar questions references to the UITextInput protocol, but I wasnt sure what I found there would be applicable here.
Any thoughts?
Thanks.
Example for UITextField
Connect the UITextField delegate to the app delegate, and add the following code:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if([string isEqual:#"b"]) {
textField.text = [textField.text stringByReplacingCharactersInRange:range withString:#"!"];
return NO;
} else {
return YES;
}
}
Every time the user presses the 'b' key it shows an exclamation mark.
All other keys work as usual.
This way you can also change the case.
For example, when the user hits 'b', update the field with 'B'.
It's currently not possible to press the shift key by code.
Related
I have an iPad app (XCode 6.1, iOS 8.1.1, ARC and Storyboards). In one of the classes (scene) I have this code:
-(BOOL) textFieldShouldBeginEditing:(UITextField *)textField { // (prevents keyboard from showing)
if(textField.tag == 200) { // birthdate
[self showModalCalendar:(int)textField.tag];
return NO; // don't show k/b
}
else
return YES;
}
It is executed when the first textField is selected, going through each textField rather than wait until each textField is selected. This is a problem because what I want to accomplish is to show the modal calendar only when a particular UITextField (birthdate) has been selected, and NOT show the keyboard. What's happening is when I tap the tab key (on a hard keyboard) the modal calendar also apprears for each textField.
Is there any way to prevent this?
The problem in my case was IQKeyboardManager library. I removed this library from my project and DownPicker works fine.
Rather than using tags you should create an IBOutlet to the particular textView that you want to handle. Then you can test if textView == your special one in the delegate callback.
The reason is that it is more explicit and clearer than using tags. My guess is that some of your other text views have that same tag for some reason and so your conditional isn't behaving as you think it should.
I have a custom view with several NSTextField controls for which I want to supply custom auto-completions and I have successfully implemented all that using the NSTextFieldDelegate Protocol. The auto-completions are full names or place names, depending on which text field is being edited.
The issue is that the auto-completions almost always contain a space character and so if the user is typing something that matches a suggestion, but doesn't want to accept that suggestion, the field editor will accept the suggestion when user presses the space key. I want the field editor to accept the suggestion using the tab key only.
I understand that this will involve subclassing NSTextView to provide a custom field editor, and this is documented by Apple as being the acceptable way, however it's not clear to me what methods I need to override and what the overridden methods need to do, in order to get me what I want.
Can anyone suggest how this is achieved?
I admit that I'd been fumbling with this question for quite some time earlier, before I discovered an acceptable answer via Google-fu. The magic code, blatantly stolen from the original answerer:
#interface MLFieldEditor : NSTextView #end
#implementation MLFieldEditor
- (void)insertCompletion:(NSString *)word forPartialWordRange:(NSRange)charRange movement:(NSInteger)movement isFinal:(BOOL)flag {
// suppress completion if user types a space
if (movement == NSRightTextMovement) return;
// show full replacements
if (charRange.location != 0) {
charRange.length += charRange.location;
charRange.location = 0;
}
[super insertCompletion:word forPartialWordRange:charRange movement:movement isFinal:flag];
if (movement == NSReturnTextMovement)
{
[[NSNotificationCenter defaultCenter] postNotificationName:MLSearchFieldAutocompleted object:self userInfo:nil];
} }
#end
(Additional reference)
I'm trying to create a simple data entry application for the Mac (not iPhone).
It has an NSTableView (inside an NSScrollView) which contains an NSTextField into which the user keys data, line by line.
What I have so far is working okay, except that in order to actually be able to type anything into the text field, you have to press the 'Enter' key first. Then when you press the 'Enter' key at the end, the field leaves 'edit' mode, but the highlight just sits on the same row. Having to press 'Enter', 'down arrow', 'Enter' between each row of data entered is not very efficient.
What I would prefer is that when you press the 'Enter' key, the cursor moves down to the next row and the field is automatically placed into 'edit' mode.
I had thought that such a thing should be fairly straightforward, but I have been tearing my hair out all evening without any progress, and I still don't even know which way I should be heading.
I tried looking for some attribute settings in the 'inspectors' in Xcode, but couldn't see anything.
Searching online, suggestions seemed to keep coming back to somehow using First Responder. Playing around with this, I've only managed to get the blue highlight ring to move down around my TableView on demand, but this still doesn't seem to put the text field into 'edit' mode nor move to another line.
Finally I found some information about subclassing and implementing your own textfields, but this seems like a very long and convoluted way to achieve this.
Can someone point me in the right direction as to how to do this?
Am I maybe using the wrong kind of control? Perhaps something else is more appropriate. Note that each line is distinct, so I don't want things like word wrap etc.
You were on the right track with the First Responder, but it's important to not that text editing is handled by the window's Field Editor (an instance of NSTextView), which becomes the first responder when text editing begins. The field editor informs its client (in this case your Table View) during editing.
The easiest way to handle this would be to subclass NSTableView and override its textDidEndEditing: method. Something like this should get you started:
- (void) textDidEndEditing:(NSNotification *)notification {
[super textDidEndEditing:notification];
int textMovement = [[notification.userInfo valueForKey:#"NSTextMovement"] intValue];
if (NSReturnTextMovement == textMovement) {
NSText *fieldEditor = notification.object;
// The row and column for the cell that just ended editing
NSInteger row = [self rowAtPoint:fieldEditor.frame.origin];
NSInteger col = [self columnAtPoint:fieldEditor.frame.origin];
if (++col >= self.numberOfColumns) {
col = 0;
if (++row >= self.numberOfRows) return;
}
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:row]
byExtendingSelection:NO];
[self editColumn:col row:row withEvent:nil select:YES];
}
}
This will progress left-to-right, top-to-bottom through the table's columns and rows. Though you said you were using a NSTextField, I presume you meant you were using a NSTextFieldCell in a cell-based table. That's another important distinction. If you're not familiar with the concept of the field editor, I suggest you read up.
I have an application, which open popover with NSTextField. The text field is not editable. Behavior for text field is set to Editable. I still can paste and copy text to this field but i can't edit it.
Anyone knows, what can be wrong?
Not sure if you still need the answer, but there may be some others still looking. I found a solution on apple developer forums. Quoting the original author:
The main problem is the way keyboard events works. Although the NSTextField (and all its superviews) receives keyboard events, it doesn't make any action. That happens because the view where the popover is atached, is in a window which can't become a key window. You can't access that window in any way, at least I couldn't. So the solution is override the method canBecomeKeyWindow for every NSWindow in our application using a category.
NSWindow+canBecomeKeyWindow.h
#interface NSWindow (canBecomeKeyWindow)
#end
NSWindow+canBecomeKeyWindow.m
#implementation NSWindow (canBecomeKeyWindow)
//This is to fix a bug with 10.7 where an NSPopover with a text field cannot be edited if its parent window won't become key
//The pragma statements disable the corresponding warning for overriding an already-implemented method
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
- (BOOL)canBecomeKeyWindow
{
return YES;
}
#pragma clang diagnostic pop
#end
That makes the popover fully resposive. If you need another window which must respond NO to canBecomeKeyWindow, you can always make a subclass.
I struggled with this for a while as well, until I realized it was a bug.
However, instead of relying on an isActive state of a NSStatusItem view, I find it much more reliable to use the isShown property of the NSPopover you have implemented.
In my code, I have a NSPopover in a NSViewController:
- (BOOL)canBecomeKeyWindow
{
if([self class]==NSClassFromString(#"NSStatusBarWindow"))
{
NSPopover *mainPopover = [[((AppDelegate*)[NSApp delegate]) mainViewController] mainPopover];
if(![mainPopover isShown])
return NO;
}
return YES;
}
Balazs Toth's answer works, but if you're attaching the popover to NSStatusItem.view the status item becomes unresponsive - requiring two clicks to focus.
What i found when working with this solution is that when NSStatusItem becomes unresponsive, you can easily override this behavior like this
- (BOOL)canBecomeKeyWindow {
if([self class]==NSClassFromString(#"NSStatusBarWindow")) {
CBStatusBarView* view = [((CBAppDelegate*)[NSApp delegate]) statusItemView];
if(![view isActive]) return NO;
}
return YES;
}
You will check for the class of the window, if it matches the NSStatusBarWindow we can then check somehow if the NSStatusItem is active. If it is, that means we have to return YES, because this way the NSPopover from NSStatusItem will have all keyboard events.
What I'm using for checking if the NSStatusItem was clicked (or is active) is that in my own custom view i have a bool value which changes when user clicks on the NSStatusItem, system automatically checks for "canBecomeKeyWindow" and when it does it will return NO and after user clicks on it (while it is returning the NO) it will change the bool value and return YES when system asks again (when NSPopover is being clicked for NSTextField editing).
Sidenotes:
CBStatusBarView is my custom view for NSStatusItem
CBAppDelegate is my App Delegate class
If anyone is still looking for an answer to this, I am working in Swift.
At the time where you wish the field to allow text entry, I have used myTextField.becomeFirstReponder()
To opt out; just use myTextField.resignFirstResponder()
Definitely a bug. That bug report is exactly what I was trying to do. Even down to creating the status item and overriding mousdown.
I can confirm that Balazs Toth's answer works. I just wonder if it might get in the way down the road.
If someone gets it and the solution above didn't do the trick for him.
The problem in my app was in the info tab in the targets my application was set to
Application is background only = true
and shulde of been
Application is agent = true
Spent an entire day on this thing.
Bug. http://openradar.appspot.com/9722231
I want to disallow dropping anything into my NSTextField. In my app, users can drag and drop iCal events into a different part of the GUI. Now I've had a test user who accidentally dropped the iCal event into the text field – but he didn't realize this because the text is inserted in the lines above the one that I see in my one-line text field.
(You can reveal the inserted text by clicking into the text field and using the keyboard to go one line up – but a normal user wouldn't do this because he/she wouldn't even have realized that something got inserted in the first place!)
I tried registerForDraggedTypes:[NSArray array]] (doesn't seem to have any effect) as well as implementing the draggingEntered: delegate method returning NSDragOperationNone (the delegate method isn't even invoked).
Any ideas?
EDIT: Of course dropping something onto an NSTextField only works when it has focus, as described by ssp in his blog and in the comments to a blog entry by Daniel Jalkut.
I am glad you discovered the comments in my blog post. I think they are the tip of the iceberg to discovering how to achieve what you're looking for.
You need to keep in mind that the reason dragging to an NSTextField works when it has focus, is that the NSTextField has itself been temporarily obscured by a richer, more powerful view (an NSTextView), which is called the "Field Editor."
Check out this section of Apple's documentation on the field editor:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/TextEditing/Tasks/FieldEditor.html
To achieve what you're striving for, I think you might need to intercept the standard provision of the field editor for your NSTextFields, by implementing the window delegate method:
windowWillReturnFieldEditor:toObject:
This gives you the opportunity to either tweak the configuration on the NSTextView, or provide a completely new field editor object.
In the worst case scenario, you could provide your own NSTextView subclass as the field editor, which was designed to reject all drags.
This might work: If you subclass NSTextView and implement -acceptableDragTypes to return nil, then the text view will be disabled as a drag destination. I also had to implement the NSDraggingDestination methods -draggingEntered: and -draggingUpdated: to return NSDragOperationNone.
#implementation NoDragTextView
- (NSArray *)acceptableDragTypes
{
return nil;
}
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
return NSDragOperationNone;
}
- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
{
return NSDragOperationNone;
}
#end
I was able to solve this problem by creating a custom NSTextView and implementing the enter and exit NSDraggingDestination protocol methods to set the NSTextView to hidden. Once the text field is hidden the superview will be able to catch the drag/drop events, or if the superview doesn't implement or want the drag/drop they are discarded
For example:
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
//hide so that the drop event falls through into superview's drag/drop view
[self setHidden:YES];
return NSDragOperationNone;
}
- (void)draggingExited:(id<NSDraggingInfo>)sender {
//show our field editor again since dragging is all over with
[self setHidden:NO];
}
Have you tried - (void)unregisterDraggedTypes from NSView?