I have an OpenGL ES app which uses keyboard. I can make the keyboard pop-up on screen when screen is touched. If I am correct, each time I press a key,
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
should be called. But it doesn't. The app was initially a pure OpenGL Mac game, which I am trying to make an iOS version of, so I am not using storyboard. I prefer to do everything programmatically if possible. Here is my code for ViewController.h:
#import <GLKit/GLKit.h>
#import "KeyboardView.h"
#interface ViewController : GLKViewController {
KeyboardView* keyBoard;
}
#end
relevant parts of ViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!self.context) {
NSLog(#"Failed to create ES context");
}
GLKView *view = (GLKView *)self.view;
view.context = self.context;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
CGRect viewRect = CGRectMake(0, 0, 100, 100);
keyBoard = [[KeyboardView alloc] initWithFrame:viewRect];
[self setupGL];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.view addSubview:keyBoard];
[keyBoard becomeFirstResponder];
}
KeyboardView.h:
#import <UIKit/UIKit.h>
#interface KeyboardView : UIView <UIKeyInput, UITextFieldDelegate> {
UITextField *field;
}
KeyboardView.m:
#import "KeyboardView.h"
#implementation KeyboardView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
field = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 100, 10)];
}
return self;
}
- (void)insertText:(NSString *)text {
}
- (void)deleteBackward {
}
- (BOOL)hasText {
return YES;
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSLog(#"text: %#", textField.text);
NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
if ([newString length] < 1) {
return YES;
} else
{
textField.text = [newString length] > 1 ? [newString substringToIndex:1] : newString;
[textField resignFirstResponder];
return NO;
}
}
#end
I need to be able to get each character entered by user while keyboard is active. I most confess, I am a little bit confused. I am not sure if my approach is correct, so I really appreciate your help.
The keyboard text input isn't captured through the UITextField's
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
It is indeed captured through UIKeyInput's
- (void)insertText:(NSString *)text;
I am using the soft keyboard to capture text on my GLKView (OpenGLES) app, and I don't even need any UITextField at all
For reference:
UIKeyInput Reference Doc
EDIT:
You need to call your keyboardview's becomeFirstResponder to show your keyboard and call resignFirstResponder to hide it
You also need to override canBecomeFirstResponder and return TRUE
Related
I've been pulling my hair for hours now regarding my xib popup acting weird in iOS 10 and above, this problem happens when textFieldDidBeginEditing is fired, here is video of the issue. Code for xib popup:
After a few times debugging this problem. I noticed that the textFieldDidBeginEditing not cause this weird issue. What I suspect is a MJPopupViewController which caused this issue.
#import "NewPopView.h"
#import "UIViewController+MJPopupViewController.h"
#interface NewBottlePopView() <UITextFieldDelegate>
#property (strong, nonatomic) UIViewController *viewController;
#end
#implementation NewBottlePopView
#synthesize viewController;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
-(void)showPopUpInViewController:(UIViewController *)controller{
if ([controller class] == [UINavigationController class]) {
viewController = [(UINavigationController*)controller visibleViewController];
[viewController setKeepOnTouchOutside:NO];
[viewController presentPopupView:self animationType:MJPopupViewAnimationSlideBottomBottom];
}else{
viewController = controller;
[viewController setKeepOnTouchOutside:NO];
[viewController presentPopupView:self animationType:MJPopupViewAnimationSlideBottomBottom];
}
}
- (IBAction)close:(id)sender {
[viewController dismissPopupViewControllerWithanimationType:MJPopupViewAnimationSlideBottomBottom];
}
#pragma mark Text Field Delegate
-(void)textFieldDidBeginEditing:(UITextField *)textField {
NSLog(#"screen width: %f", [[UIScreen mainScreen] applicationFrame].size.width);
if ([[UIScreen mainScreen] applicationFrame].size.width <= 320)
{
//CGFloat newY = -textField.frame.origin.y - self.frame.size.height;
CGFloat newY = -textField.frame.origin.y + 140;
if (newY >= self.frame.origin.y)
newY = self.frame.origin.y;
[UIView animateWithDuration:0.3 animations:^{
self.frame = CGRectMake(self.frame.origin.x,
newY,
self.frame.size.width,
self.frame.size.height); //resize
}];
}
}
-(void)textFieldDidEndEditing:(UITextField *)textField {
if (textField.tag == 202)
{
NSError *error;
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:#"[^\\d]" options:NSRegularExpressionCaseInsensitive error:&error];
textField.text = [regex stringByReplacingMatchesInString:textField.text options:0 range:NSMakeRange(0, [textField.text length]) withTemplate:#""];
}
textField.text = [textField.text uppercaseString];
[textField resignFirstResponder];
// [self tapBackground:[self.gestureRecognizers firstObject]];
}
-(BOOL)textFieldShouldReturn:(UITextField*)textField;
{
textField.text = [textField.text uppercaseString];
NSInteger nextTag = textField.tag + 1;
// Try to find next responder
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (nextResponder) {
// Found next responder, so set it.
[nextResponder becomeFirstResponder];
} else {
// Not found, so remove keyboard.
[textField resignFirstResponder];
[self tapBackground:[self.gestureRecognizers firstObject]];
}
return NO; // We do not want UITextField to insert line-breaks.
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
textField.text = [textField.text uppercaseString];
return YES;
}
-(void)tapBackground:(UIGestureRecognizer*)getsure{
[self endEditing:YES];
[UIView animateWithDuration:0.3 animations:^{
[self setCenter:viewController.view.center];
}];
}
#end
I hope someone can help me with this problem as I cannot thinking anymore. Thanks in advance.
Finally I found a solution for this problem by following this post :
iOS 11 - Keyboard Height is returning 0 in keyboard notification
I am facing the the wierdest bug (ether in my app or in IOS 7.1) ever.
After many hours I managed to create a simple app that demonstrate the problem.
Two UITextField - Dragged and dropped from interface builder and wired to t1, t2.
the ViewController:
#implementation ViewController
#synthesize t1;
#synthesize t2;
#pragma mark - UITextFieldDelegate
-(void)textFieldDidBeginEditing:(UITextField *)iTextField {
NSLog(#"textFieldDidBeginEditing");
[iTextField performSelector:#selector(selectAll:) withObject:iTextField afterDelay:0.0];
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
return YES;
}
- (void)viewDidLoad
{
[super viewDidLoad];
t1.delegate = self;
t2.delegate = self;
}
#end
When tapping on t1 and t2 at the same time, both textFields become first responder in an endless loop!
When omitting ether the PerformSelector statement or the textField:shouldChangeCharactersInRange: implementation, problem is gone.
Can someone explain why does it happen?
Edit: Also set the exclusiveTouch property of each UITextField to: YES to prevent them from editing at the same time.
- (void)viewDidLoad
{
[super viewDidLoad];
t1.exclusiveTouch = YES;
t2.exclusiveTouch = YES;
t1.delegate = self;
t2.delegate = self;
}
- (void)textFieldDidBeginEditing:(UITextField *)iTextField
{
[iTextField performSelector:#selector(selectAll:) withObject:nil afterDelay:0.0];
}
Or more simply without using the exclusiveTouch properties:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)iTextField
{
if (iTextField == t1 && t2.isFirstResponder == NO)
{
return YES;
}
else if (iTextField == t2 && t1.isFirstResponder == NO)
{
return YES;
}
return NO;
}
I try to use the selectedTextRange property to instead of selectAll, it makes the endless loop's problem gone.
func textFieldDidBeginEditing(_ textField: UITextField) {
DispatchQueue.main.async {
let begin = textField.beginningOfDocument
let end = textField.endOfDocument
textField.selectedTextRange = textField.textRange(from: begin, to: end)
}
}
I set UITextFieldDelegate in my .h and in my .m have #property (strong, nonatomic) IBOutlet UITextField *myPhoneNumber;
In my viewDidLoad method I have
[self.myPhoneNumber setDelegate:self];
[self.myPhoneNumber addTarget:self
action:#selector(editingChanged:)
forControlEvents:UIControlEventEditingChanged];
My editingChanged: method listens to make sure the text input is >9 characters and then enables a button
- (IBAction)editingChanged:(id)sender {
if ([self.myPhoneNumber.text length] <= 9) {
self.myButton.enabled = NO;
}
else {
self.myButton.enabled = YES;
}
}
This works exactly how I want it to but when I press return on the keyboard to hide it this crashes the app with the error:
2014-07-15 08:08:27.089 sample-chat[47702:90b] -[MyViewController editingChanged]: unrecognized selector sent to instance 0x10ba7420
2014-07-15 08:08:27.093 sample-chat[47702:90b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyViewController editingChanged]: unrecognized selector sent to instance 0x10ba7420'
My method for returning the keyboard is as follows
-(BOOL) textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder];
return YES;
}
You specified the return type of editingChanged: as IBAction. This is only required for linking up this method in Interface Builder, which would be redundant since you manually added the UIControlEventEditingChanged listener in viewDidLoad.
Either use
- (void)editingChanged:(id)sender
OR
use the method as is, but link it to the UIControlEventEditingChanged in Interface Builder.
Try this Way #mostly
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSUInteger length = editingTextField.text.length - range.length + string.length;
if (length > 0) {
yourButton.enabled = YES;
} else {
yourButton.enabled = NO;
}
return YES;
}
oky u can also try do this by using NSNotificationCenter, for example u want t check each character that entered to text field then u can try this
for example
//add the observer
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(editingChanged:) name:UITextFieldTextDidChangeNotification object:nil];
}
//remove the observer
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
}
- (void)editingChanged:(id)sender
{
if ([self.myPhoneNumber.text length] <= 9) {
self.myButton.enabled = NO;
}
else {
self.myButton.enabled = YES;
}
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder];
return YES;
}
I think there is no need to use a custom selector like you done. It is better to use the text field delegate methods to manage this.
Set the delegate [self.myPhoneNumber setDelegate:self];
And implement the delegate methods like :
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
self.myButton.enabled = NO;
if (textField == _myPhoneNumber)
{
if (textField.text.length > 9)
{
self.myButton.enabled = YES;
}
}
}
-(BOOL) textFieldShouldReturn:(UITextField *)textField
{
[self.view endEditing:YES];
return YES;
}
is strange to se no question about this problem in all stack overflow, but i can't find it :(
So let me explain. I have a simple UITextField that should present a custom inputAccessoryView.
The expected behavior is:
tap in the textfield (ACTextField)
show keyboard with a textfield and a button (with cancel title)
type in some text (it have to appear in the UITextField named
inputField )
press enter and see the text appearing in the main textfield
in case i press cancel the keyboard should disappear and let the main textfield unchenged
pretty much like the Message App from Apple
And all works well exept 2 things:
if i spam key tapping on the keyboard (just to enter random text ;)
) the app freeze and Xcode show me CPU at 99% (no recovery possible)
sometimes the memory occupation grow from 2/3 MB to 40 MB just after
pressing enter and the app freeze again
Let's show some code
the .h
#import <UIKit/UIKit.h>
#interface ACTextField : UITextField
#end
the .m
#import "ACTextField.h"
#interface ACTextField () <UITextFieldDelegate>
#property (nonatomic, retain) UITextField *inputField;
#property (nonatomic, assign) BOOL keyboardOpen; // to check if keyboard is open
#property (nonatomic, retain) NSTimer *timer; //added after an answer
#end
#implementation ACTextField
// method called when timer fire
- (void) fireTimer:(NSTimer *) timer
{
[self.timer invalidate];
self.timer = nil; // this line thoesen't change anyting so can be deleted
[self.inputField becomeFirstResponder];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self awakeFromNib];
}
return self;
}
-(void)awakeFromNib
{
self.delegate = self;
self.keyboardOpen = NO;
}
- (void)cancelPressed:(id)sender
{
[self.inputField resignFirstResponder];
[self resignFirstResponder];
self.keyboardOpen = NO;
}
- (void)dealloc
{
NSLog(#"dealloc ACTextField %#", self);
self.inputField = nil;
self.timer = nil;
}
- (void) designAccessoryView
{
if (self.inputAccessoryView == nil) {
CGRect frame = [[UIScreen mainScreen] bounds];
self.inputAccessoryView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, 44)];
self.inputAccessoryView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
int buttonWidth = 65;
int padding = 5;
UIButton *cancel = [UIButton buttonWithType:UIButtonTypeCustom];
cancel.frame = CGRectMake(padding, 0, buttonWidth, 44);
[cancel setTitle:#"cancel" forState:UIControlStateNormal];
[cancel setTitle:#"cancel" forState:UIControlStateHighlighted];
[cancel addTarget:self action:#selector(cancelPressed:) forControlEvents:UIControlEventTouchUpInside];
[self.inputAccessoryView addSubview:cancel];
self.inputField = [[UITextField alloc] initWithFrame:CGRectMake(buttonWidth + 2*padding, 2, frame.size.width - buttonWidth - 3*padding, 40)];
self.inputField.borderStyle = UITextBorderStyleRoundedRect;
self.inputField.delegate = self;
[self.inputAccessoryView addSubview:self.inputField];
self.inputAccessoryView.backgroundColor = [UIColor lightGrayColor];
}
}
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField{
return YES;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
if (!self.keyboardOpen && textField == self)
{
self.inputField.text = self.text;
[self designAccessoryView];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1
target:self // modified to use the instance method fireTimer
selector:#selector(fireTimer:)
userInfo:nil
repeats:NO];
self.keyboardOpen = YES;
}
}
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
return YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
return YES;
}
- (BOOL)textFieldShouldClear:(UITextField *)textField
{
return YES;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
self.text = self.inputField.text;
[self cancelPressed:self];
return YES;
}
#end
Any help will be greatly appreciated
EDIT: I forgot to specify that i'm testing this on my iPhone 4s with IOS 7.0.4
EDIT2: I tried using an UITextField Wrapper with pretty much the same code and all goes well. The behavior is the expected and the keyboard don't freeze anymore and the memory remain low as should be. Ok, but i need a UITextField subclass, not a wrapper :(
I have a custom view, i want display a keyboard as my input and intercommunicate with it.
thanks guys~
In your custom view you must add a zero-sized UITextField and then set your view as its delegate. Then in your custom view define a gesture recognizer (e.g. a single tap) and in the gesture recognizer recognition event set the text field as first responder.
As soon as you tap the view the keyboard will popup. Then write the delegate method:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
to manage the interaction with the typed string. Don't forget to call setNeedsDisplay if you need to update your view on the user keyboard typing.
The example below works. You must instantiate the custom view in your view controller of course. With the example, just type a letter in the keyboard and it will be displayed on the view.
//
// MyView.h
//
#import
#interface MyView : UIView {
UITextField *tf;
}
#property (nonatomic,copy) NSString *typed;
#end
//
// MyView.m
//
#import "MyView.h"
#implementation MyView
#synthesize typed;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor=[UIColor redColor];
tf = [[UITextField alloc] initWithFrame:CGRectZero];
tf.delegate=self;
[self addSubview:tf];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tap:)];
[self addGestureRecognizer:tap];
}
return self;
}
// Only override drawRect: if you perform custom drawing.
- (void)drawRect:(CGRect)rect
{
// Draw the "typed" string in the view
[[UIColor blackColor] setStroke];
if(self.typed) {
[self.typed drawAtPoint:CGPointZero withFont:[UIFont systemFontOfSize:50]];
}
}
-(void)tap:(UIGestureRecognizer *)rec {
if(rec.state==UIGestureRecognizerStateEnded) {
[tf becomeFirstResponder];
}
}
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
self.typed=[string substringToIndex:1]; // extract the first character of the string
[self setNeedsDisplay]; // force view redraw
}
#end
I think the solution could be as simple as implementing the UIKeyInput protocol in your class.
Read more about the UIKeyInput Protocol Reference.