I have a form with a few text fields on a scroll view. I was trying to solve the problem of the keyboard hiding some text fields, which I partly did. At least it works well when I tap on each individual field. I used the recommended Apple approach:
I have registered for keyboard notifications in viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:self.view.window];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:self.view.window];
I am tracking active text fields:
- (void)textFieldDidBeginEditing:(UITextField *)textField {
activeTextField = textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
activeTextField = nil;
}
I am scrolling the view up when the keyboard shows up:
- (void)keyboardWillShow:(NSNotification *)aNotification {
// Get the size of the keyboard
NSDictionary* info = [aNotification userInfo];
keyboardHeight = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardHeight, 0.0);
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
// If active text field is hidden by keyboard, scroll it so it's visible
CGRect aRect = self.view.frame;
aRect.size.height -= keyboardHeight + 44 + 44; // Compensates for Navbar and text field height
if (!CGRectContainsPoint(aRect, activeTextField.frame.origin) ) {
[scrollView scrollRectToVisible:activeTextField.frame animated:YES];
}
}
I then scroll the view back to default when the keyboard is hidden (I won't paste the code simply because it works fine).
However, out of my 5 text fields, the first 4 have a Next button on the keyboard (instead or Return) while the last field has Done. The idea is that I want the user to jump from one text field to another (in one direction is enough in my case). So, I've implemented a UITextField delegate method to handle that as well:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if (textField == firstNameTextField) {
[lastNameTextField becomeFirstResponder];
} else if (textField == lastNameTextField) {
[countryTextField becomeFirstResponder];
} else if (textField == cityTextField) {
[zipCodeTextField becomeFirstResponder];
} else if (textField == zipCodeTextField) {
[zipCodeTextField resignFirstResponder];
}
return NO;
}
The middle text field above is skipped, because for that text field I'm using a different input type (a custom view with a UIPickerView and a bar on top with Next button) - the missing code is in this method:
- (IBAction)goToNextTextField:(id)sender {
[cityTextField becomeFirstResponder];
}
OK, as I've mentioned, view adjustment works well when tapping individual text fields (and then dismissing the keyboard), even though keyboard sizes (standard iOS vs my custom view) are different heights. I can also successfully go through all text fields tapping on Next buttons.
Here are my issues though:
When tapping on Next, if the keyboard is not changing (say, from field 4 to 5 that both use standard keyboard), my keyboardWillShow: method is not called, NSLog debugger shows keyboardHeight as 0, and yet the view moves up unpredictably.
Also, when navigating to and from field 3 (the one that uses custom input view), keyboardHeight is thus not recalculated. I have tried registering to UIKeyboardDidChangeFrameNotification and UIKeyboardWillChangeFrameNotification (pointing to keyboardWillShow: method), but without much success. It is worth noting that I do see in console that keyboardHeight is changing, but it's usually lagging one step, i.e. keyboardHeight is updated when I leave the field, not when it becomeFirstResponder.
Perhaps a pair of experienced eyes will spot my mistake, for I have been destroying my pair of eyes searching for the solution for the last 2 days..
Thanks!
You can use UITextField delegates . Whenever user start editing in any textfield its delegate is called you can change scrollview offset using
- (void)scrollViewToCenterOfScreen:(UIView *)theView
{
CGFloat viewCenterY = theView.center.y;
CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
CGFloat availableHeight = applicationFrame.size.height - 200; // Remove area covered by keyboard
CGFloat y = viewCenterY - availableHeight / 2.0;
if (y < 0)
{
y = 0;
}
[scrollView setContentOffset:CGPointMake(0, y+20) animated:YES];
}
So in TextField delegate you can set
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
if([textField isEqual:textfield1])
{
[self scrollViewToCenterOfScreen:textfield1];
}
else if([textField isEqual:textfield2])
{
[self scrollViewToCenterOfScreen:textfield2];
}
return YES;
}
and when user press done or return button you can change offset to (0,0)
[scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
Hope this works for you.
https://github.com/simonbs/BSKeyboardControls
this control can show a toolbar above the keyboard when editing a textfield like this
==========================================
-(void)textFieldDidBeginEditing:(UITextField *)textField {
if (textField == firstNameTextField) {
//move to firstNameTextField
} else if (textField == lastNameTextField) {
//move to lastNameTextField
} else if (textField == cityTextField) {
//move to cityTextField
} else if (textField == zipCodeTextField) {
//move to zipCodeTextField
}
return NO;
}
I have in the end implemented a solution that works well for me. It's a mixture of Apple recommended approach, a couple of solutions found on these forums as well as my own.
First of all, register as UITextFieldDelegate on view controller .h file.
Then in viewDidLoad register for keyboard notifications:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:self.view.window];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:self.view.window];
Don't forget to unregister from them (removeObserver:) in viewDidUnload method as well.
Let your application know which text field is currently active:
- (void)textFieldDidBeginEditing:(UITextField *)textField {
activeTextField = textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
activeTextField = nil;
}
When your application receives a notification that keyboard will show, it calls this method:
- (void)keyboardWillShow:(NSNotification *)aNotification {
keyboardHeight = [[[aNotification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
[scrollView setFrame:CGRectMake(scrollView.frame.origin.x,
scrollView.frame.origin.y,
scrollView.frame.size.width,
scrollView.frame.size.height - keyboardHeight)];
[self moveViewWithKeyboard];
}
All I'm doing here is decreasing the size of my scrollView frame by the size of the keyboard. After that I call my moveViewWithKeyboard: method that will make active view visible.
Interestingly enough, to solve the problem of correct keyboard height being detected one step too late (read my original question above), I had to change the argument UIKeyboardFrameBeginUserInfoKey in keyboard height detection line with UIKeyboardFrameEndUserInfoKey argument. Having done so, keyboard (or custom view) is detected when moving to the relevant field, not when moving away from it.
Of course, I have to restore original view frame when the keyboard hides (and note that I'm still calling the moveViewWithKeyboard: method with nil as an argument):
- (void)keyboardWillHide:(NSNotification *)aNotification {
[scrollView setFrame:CGRectMake(scrollView.frame.origin.x,
scrollView.frame.origin.y,
scrollView.frame.size.width,
scrollView.frame.size.height + keyboardHeight)];
[self moveViewWithKeyboard];
}
I also added calls to move view when keyboard hide/show is not triggered. This happens when I jump from text field to text field that have system keyboard as input, so it is neither hidden nor shown again (and no notifications are issued to trigger delegate methods).
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if (textField == firstNameTextField) {
[lastNameTextField becomeFirstResponder];
} else if (textField == lastNameTextField) {
[countryTextField becomeFirstResponder];
} else if (textField == cityTextField) {
[zipCodeTextField becomeFirstResponder];
} else if (textField == zipCodeTextField) {
[zipCodeTextField resignFirstResponder];
}
[self moveViewWithKeyboard:nil];
return NO;
}
And finally my method to move view:
- (void)moveViewWithKeyboard {
if (activeTextField) {
[scrollView scrollRectToVisible:activeTextField.frame animated:YES];
} else {
[scrollView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
}
}
Note that when no text field is passed as an argument (i.e. no text field is active), view is scrolled to its original position.
Hope this helps someone.
Related
Im working on getting my UIscroll to scroll when ever a textfield is blocked by the keyboard by following this documentation
http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html#//apple_ref/doc/uid/TP40009542-CH5-SW7
However sadly...there is a variable mentioned, activeField and i cannot figure out how it is declared. I would really like if some probably advise how/where it is declared or even a solution to scrolling when the keyboard is activated will help.
Thank you
To answer your specific question, since the Apple Documentation is only ever using activeField for size data, you can simply declare it as a private global UIView *activeField and it will work for textFields, textViews, etc all the same.
However, their code actually doesn't work very well at all. I had to make some changes to their code to get mine to work correctly. This code is basically theirs with some minor tweaks to handle all of the cases listed below:
1) If you have a smaller scrollView nested inside of a view, not full screen scrollView and it still works.
2) If you want to move text fields down to the keyboard focus area as well as move them up from behind the keyboard.
3) Works on textViews and textFields of various sizes
4) If the keyboard is currently showing, it will move any newly tapped fields into the focus area
5) Works if you have your content already scrolled and invoke the keyboard
6) Works on all keyboard sizes for all devices (no hardcoded constants)
First off, create private variables for these:
UIView *_activeField;
CGFloat _keyboardHeight;
BOOL _isShowingKeyboard;
Next, just cut and paste this code into your ViewController (it looks like a lot but it's not that bad).
#pragma mark TextFieldKeyboardScrolling
- (void)registerForKeyboardNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWasShown:) name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillBeHidden:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)adjustInputFieldsForKeyboard {
CGFloat keyBoardTopInScrollView = self.view.frame.size.height - _keyboardHeight - self.scrollView.frame.origin.y;
CGFloat inputFieldBottomInVisibleScrollView = _activeField.frame.origin.y - self.scrollView.contentOffset.y + 30 /* small buffer for cursor size */;
CGPoint scrollPoint;
if (keyBoardTopInScrollView > inputFieldBottomInVisibleScrollView) {
scrollPoint = CGPointMake(0.0, self.scrollView.contentOffset.y - (keyBoardTopInScrollView - inputFieldBottomInVisibleScrollView));
} else {
scrollPoint = CGPointMake(0.0, self.scrollView.contentOffset.y + (inputFieldBottomInVisibleScrollView - keyBoardTopInScrollView));
}
[self.scrollView setContentOffset:scrollPoint animated:YES];
}
- (void)keyboardWasShown:(NSNotification*)aNotification {
_isShowingKeyboard = YES;
NSDictionary* info = [aNotification userInfo];
_keyboardHeight = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, _keyboardHeight, 0.0);
self.scrollView.contentInset = contentInsets;
self.scrollView.scrollIndicatorInsets = contentInsets;
[self adjustInputFieldsForKeyboard];
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification {
_isShowingKeyboard = NO;
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
self.scrollView.contentInset = contentInsets;
self.scrollView.scrollIndicatorInsets = contentInsets;
}
#pragma mark UITextFieldDelegate
- (void)textFieldDidBeginEditing:(UITextField *)textField {
_activeField = textField;
if (_isShowingKeyboard) {
[self adjustInputFieldsForKeyboard];
}
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
_activeField = nil;
}
That's it, just call [self registerForKeyboardNotifications]; in your viewDidLoad method, hook up your scrollView outlet and textField/textView delegates in storyboard and you're all set.
The keyboard is only active when one of the texfields become active(Became first responder).So you should listen to UITextField delegate method to see when and which uitextfield became first responder.
- (void)textFieldDidBeginEditing:(UITextField *)textField
if you assign a tag to your textfield or make it ivar you can also understand which uitextfield has become active and also get its frame.
I don't use the scroll view or follow the above documentation.
Here is how without using the scroll view
In my
textFieldShouldBeginEditing
CGRect frame = self.view.frame;
frame.origin.y = <some negative value>;
self.view.frame = frame
and in my
textFieldShouldEndEditing
CGRect frame = self.view.frame;
frame.origin.y = <some positive value>; // absolute value of your above negative value
self.view.frame = frame
The above works for me. Remember these are the text field delegate methods. So you need to setup your delegate.
I am doing a project on gpsnote. if anyone have any information abt this application plz guide me...
I know this code bt i want textfield should come along with my keyboard
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
}
thanks
First set your content over a scroll view and set the scroll view in viewDidLoad:
[scrollView setContentSize : CGSizeMake (320, 1040)];
then in viewWillAppear add following notification :
For keyboard shown
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
For keyboard hiding
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
Following are the two function which are called by the notifications
- (void)keyboardWasShown:(NSNotification*)aNotification {
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
double keyboardHeight = kbSize.height;
double screenHeight = [UIScreen mainScreen].bounds.size.height - 20;
if(textOrigin > screenHeight - keyboardHeight)
{
double sofset = textOrigin - (screenHeight - keyboardHeight);
CGPoint offset = scrollBackGround.contentOffset;
offset.y += sofset;
[scrollBackGround setContentOffset:offset animated:YES];
}
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
[scrollBackGround setContentOffset:CGPointMake(0, 0) animated:YES];
}
In keyboardWasShown function what we are doing is just getting the height of the keyboard and checking if the textField y axis (i.e textOrigin in the function) are greater than Y axis of keyboard than slide up the content of the scrollview which contain our text field.
NOw How to get the textfield Y axis. For this you have to use the textfield delegate, the following delegate will trigger when your textfield will become first responder
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
textOrigin = scrollBackGround.frame.origin.y + textField.frame.origin.y + 20(it is status bar height) + yourNavigationBarheight;
// Make sure to make textOrigin an ivar in your .h file
}
And finally in keyboardWillBeHidden we are reseting the scrollview contentview
I've been wondering if it is possible to replicate the behavior of Apple's iOS5 keyboard in the messages app, without using any private API calls. When you scroll down past the keyboard in the messages app, the keyboard will collapse leaving more room to see messages - try it to see.
I couldn't find anything that points towards making this without having to start jumping through some serious hoops to get an instance of the Keyboard's View. And I'm pretty sure Apple wouldn't be happy with that.
In addition to the answer given below you can see a fully baked xcode project of my implementation here:
https://github.com/orta/iMessage-Style-Receding-Keyboard
In iOS 7 there is a keyboardDismissMode property on UIScrollView.
So just set it to "UIScrollViewKeyboardDismissModeInteractive" and you'll get this behavior. Works in UIScrollView subclasses such as UITableView.
self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;
Swift 3:
tableView.keyboardDismissMode = .interactive
Or change it in storyboard (if using it) in attributes inspector for your UIScrollView subclass.
This is an incomplete solution, however it should give you a good starting point.
Add the following ivars to your UIViewController:
CGRect keyboardSuperFrame; // frame of keyboard when initially displayed
UIView * keyboardSuperView; // reference to keyboard view
Add an inputAccessoryView to your text controller. I created an small view to insert as the accessoryView:
accView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
accView.backgroundColor = [UIColor clearColor];
textField.inputAccessoryView = accView;
I added the above code to -(void)loadView
Register to receive UIKeyboardDidShowNotification and UIKeyboardDidHideNotification when view is loaded:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
return;
}
Add methods to specified as the selectors for the notifications:
// method is called whenever the keyboard is about to be displayed
- (void)keyboardWillShow:(NSNotification *)notification
{
// makes keyboard view visible incase it was hidden
keyboardSuperView.hidden = NO;
return;
}
// method is called whenever the keyboard is displayed
- (void) keyboardDidShow:(NSNotification *)note
{
// save reference to keyboard so we can easily determine
// if it is currently displayed
keyboardSuperView = textField.inputAccessoryView.superview;
// save current frame of keyboard so we can reference the original position later
keyboardSuperFrame = textField.inputAccessoryView.superview.frame;
return;
}
Add methods to track touched and update keyboard view:
// stops tracking touches to divider
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
CGRect newFrame;
CGRect bounds = [[UIScreen mainScreen] bounds];
newFrame = keyboardSuperFrame;
newFrame.origin.y = bounds.size.height;
if ((keyboardSuperView.superview))
if (keyboardSuperFrame.origin.y != keyboardSuperView.frame.origin.y)
[UIView animateWithDuration:0.2
animations:^{keyboardSuperView.frame = newFrame;}
completion:^(BOOL finished){
keyboardSuperView.hidden = YES;
keyboardSuperView.frame = keyboardSuperFrame;
[textField resignFirstResponder]; }];
return;
}
// updates divider view position based upon movement of touches
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch * touch;
CGPoint point;
CGFloat updateY;
if ((touch = [touches anyObject]))
{
point = [touch locationInView:self.view];
if ((keyboardSuperView.superview))
{
updateY = keyboardSuperView.frame.origin.y;
if (point.y < keyboardSuperFrame.origin.y)
return;
if ((point.y > updateY) || (point.y < updateY))
updateY = point.y;
if (keyboardSuperView.frame.origin.y != updateY)
keyboardSuperView.frame = CGRectMake(keyboardSuperFrame.origin.x,
point.y,
keyboardSuperFrame.size.width,
keyboardSuperFrame.size.height);
};
};
return;
}
Disclaimers:
When resigning as first responded, the keyboard moves back to its original position before sliding off screen. To make dismissing the keyboard more fluid, you first need to create an animation to move the keyboard off of the screen and then hide the view. I'll leave this part as an exercise to the readers.
I've only tested this on the iOS 5 simulator and with an iPhone with iOS 5. I have not tested this with earlier versions of iOS.
The SlidingKeyboard project I created to test this concept is available from GitHub in the examples directory of BindleKit:
https://github.com/bindle/BindleKit
Edit: Updating example to address first disclaimer.
Vladimir's simple solution will hide the keyboard as the user scrolls down. However to finish the question regarding iMessage, in order to keep a TextField always visible and anchored to the top of the keyboard, you need to implement these methods:
- (UIView *) inputAccessoryView {
// Return your textfield, buttons, etc
}
- (BOOL) canBecomeFirstResponder {
return YES;
}
Here's a good tutorial breaking it down more
I have a scrollView and i want to scroll it automatically when i select an textField
(i am filling a form here)
i am using following method to scroll it
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
[scrollView setContentOffset:CGPointMake(0,50*(textField.tag-1))];
}
Now the problem is that i have more than 10 text fields and when i reached to the seventh textField the scrollView scrolls more .
I also tried to print the CGPointMake()'s values...and it is showing correct values..but the scroller goes beyond the range what is expected..
Look at following images
The following 2 images showing control on textFields tag <7
But when control reaches to 7th textField it scrolls more
and after this it goes beyond bounds.
This problem occurs only when i move from one textFields to another without pressing that return button(i mean with resignFirstResponder).But when i press that return button and then go to the next field then all works fine.
Can anyone suggest where should be the problem..?
It won't scroll because it doesn't contain enough content. It will stop when the bottom edge of the content reaches the bottom end of the frame. Use something like
CGSize size = scrollView.contentSize;
scrollView.contentSize = CGSizeMake (size.width, size.height + ADDITIONAL_HEIGHT);
when setting up the scroll view, or in your -viewDidLoad: method, if it was loaded from a XIB.
It should keep the text field being edited on the screen - i.e. scroll automatically - so you shouldn't need to do anything?
However, it might be trying to edit a text field that is behind the keyboard - so the better solution would be:
//Get notifications of the keyboard opening and closing
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
- (void)keyboardWillShow:(NSNotification *)notification {
//Get the keyboard height
int h = [self.view convertRect:[[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue] toView:nil].size.height;
//Change the inset of the scroll view and scroll bars
scrollView.contentInset = scrollView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, h, 0);
}
- (void)keyboardWillHide:(NSNotification *)notification {
scrollView.contentInset = scrollView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, 0, 0);
}
try this.declare a variable
CGPoint svos;
in .h file and do this.
- (void)textFieldDidBeginEditing:(UITextField *)textField {
svos = scr.contentOffset;//scr is my scroll view
CGPoint pt;
CGRect rc = [textField bounds];
rc = [textField convertRect:rc toView:scr];
pt = rc.origin;
pt.x = 0;
pt.y -= 60;
[scr setContentOffset:pt animated:YES];
//NSLog(#"%f",pt.y);
}
I'm using a UITextView to roughly replicate the SMS text box above the keyboard. I'm using a UITextView instead of a field so that it can expand with multiple lines.
The problem is that, in my UITextView, the correction suggestions pop up below the text, causing them to be partially obscured by the keyboard.
In the SMS app, the suggestions pop up above the text. The placement does not appear to be a property of UITextView, or UITextInputTraits.
Any idea how to replicate this behavior? Thanks!
The problem is that the Keyboard is implemented as a separate UIWindow, rather than as a view within the main UIWindow, so layout with it is tricky. Here are some pointers in the right direction:
Hunt through the application's -windows property to find the private UITextEffectsWindow window and figure out its frame. This is the keyboard
Hunt through the TextView's subviews to find the private UIAutocorrectInlinePrompt view. This is the autocorrect bubble.
Move that subview into a separate wrapper view (added to the TextView) and then move that wrapper view so it's above the above-mentioned keyboard window.
You'll notice two mentions of "private" above. That carries all the relevant caveats. I have no idea why Apple has allowed the problem to persist when even their apps have had to work around it.
By doing the search for the UIAutocorrectInlinePrompt in an overridden or swizzled layoutSubViews it is possible to alter the layout of the correction so that it appears above. You can do this without calling any private APIs by looking for the subs views of particular classes positioned in a way you'd expect them. This example works out which view is which, checks to see that the correction is not already above the text and moves the correction above, and draws it on the window so that it is not bounded by the UITextView itself. Obviously if apple change the underlying implementation then this will fail to move correction. Add this to your overriden or swizzled layoutSubViews implementation.
- (void) moveSpellingCorrection {
for (UIView *view in self.subviews)
{
if ([[[view class] description] isEqualToString:#"UIAutocorrectInlinePrompt"])
{
UIView *correctionShadowView = nil; // [view correctionShadowView];
for (UIView *subview in view.subviews)
{
if ([[[subview class] description] isEqualToString:#"UIAutocorrectShadowView"])
{
correctionShadowView = subview;
break;
}
}
if (correctionShadowView)
{
UIView *typedTextView = nil; //[view typedTextView];
UIView *correctionView = nil; //[view correctionView];
for (UIView *subview in view.subviews)
{
if ([[[subview class] description] isEqualToString:#"UIAutocorrectTextView"])
{
if (CGRectContainsRect(correctionShadowView.frame,subview.frame))
{
correctionView = subview;
}
else
{
typedTextView = subview;
}
}
}
if (correctionView && typedTextView)
{
CGRect textRect = [typedTextView frame];
CGRect correctionRect = [correctionView frame];
if (textRect.origin.y < correctionRect.origin.y)
{
CGAffineTransform moveUp = CGAffineTransformMakeTranslation(0,-50.0);
[correctionView setTransform: moveUp];
[correctionShadowView setTransform: moveUp];
CGRect windowPos = [self convertRect: view.frame toView: nil ];
[view removeFromSuperview];
[self.window addSubview: view];
view.frame = windowPos;
}
}
}
}
}
}
Actually doing
textview.scrollEnabled = NO;
will set the bubble on top of the text... the caveat is that you lose scrolling, in my case it wasn't a problem due to havinng a textfield only for input purposes with character limit
Actually, the keyboard simply uses the result of -[UITextInput textInputView] to determine where to put the correction view (and to ask if your view supports correction). So all you need to do is this:
- (UIView *)textInputView {
for (UIWindow *window in [UIApplication sharedApplication].windows) {
if ([window isKindOfClass:NSClassFromString(#"UITextEffectsWindow")] &&
window != self.window) {
return window;
}
}
// Fallback just in case the UITextEffectsWindow has not yet been created.
return self;
}
Note that you'll likely also need to update -[UITextInput firstRectForRange:] to use the coordinate system of the window / device, so you can do this:
- (CGRect)firstRectForRange:(CoreTextTokenTextRange *)range {
CGRect firstRect = [self firstRectForRangeInternal:range];
return [self convertRect:firstRect toView:[self textInputView]];
}
(In the above context, self is a class that implements UITextInput).
If the bottom of your UITextView clears the keyboard, you should be able to just resize your UITextView to be tall enough to see the corrections. The corrections themselves don't display outside of the UITextView's frame.
If you want to mimic what you are getting in the SMS app (corrections above), you'll probably have to roll your own.
Putting the below method, adjustAutocorrectPromptView in layoutSubviews worked for me in portrait and landscape. I have a category that provides the bottom and top methods on view but you get the idea.
NSArray * subviewsWithDescription(UIView *view, NSString *description)
{
return [view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:[NSString stringWithFormat:#"class.description == '%#'", description]]];
}
- (void) adjustAutocorrectPromptView;
{
UIView *autocorrectPromptView = [subviewsWithDescription(self, #"UIAutocorrectInlinePrompt") lastObject];
if (! autocorrectPromptView)
{
return;
}
UIView *correctionShadowView = [subviewsWithDescription(autocorrectPromptView, #"UIAutocorrectShadowView") lastObject];
if (! correctionShadowView)
{
return;
}
UIView *typedTextView = nil; //[view typedTextView];
UIView *correctionView = nil; //[view correctionView];
for (UIView *subview in subviewsWithDescription(autocorrectPromptView, #"UIAutocorrectTextView"))
{
if (CGRectContainsRect(correctionShadowView.frame,subview.frame))
{
correctionView = subview;
}
else
{
typedTextView = subview;
}
}
if (correctionView && typedTextView)
{
if (typedTextView.top < correctionView.top)
{
correctionView.bottom = typedTextView.top;
correctionShadowView.center = correctionView.center;
}
}
}
Make sure your view controller delegate is listening to the notification when the keyboard pops up so that you resize your UITextView so that the keyboard doesn't obscure the UITextView. Then your correction won't be obscured by the keyboard. See:
http://www.iphonedevsdk.com/forum/iphone-sdk-development/12641-uitextview-scroll-while-editing.html
Here is a copy of the code from that page in case the original link is broken:
// the amount of vertical shift upwards keep the Notes text view visible as the keyboard appears
#define kOFFSET_FOR_KEYBOARD 140.0
// the duration of the animation for the view shift
#define kVerticalOffsetAnimationDuration 0.50
- (IBAction)textFieldDoneEditing:(id)sender
{
[sender resignFirstResponder];
}
- (IBAction)backgroundClick:(id)sender
{
[latitudeField resignFirstResponder];
[longitudeField resignFirstResponder];
[notesField resignFirstResponder];
if (viewShifted)
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:kVerticalOffsetAnimationDuration];
CGRect rect = self.view.frame;
rect.origin.y += kOFFSET_FOR_KEYBOARD;
rect.size.height -= kOFFSET_FOR_KEYBOARD;
self.view.frame = rect;
[UIView commitAnimations];
viewShifted = FALSE;
}
}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
if (!viewShifted) { // don't shift if it's already shifted
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:kVerticalOffsetAnimationDuration];
CGRect rect = self.view.frame;
rect.origin.y -= kOFFSET_FOR_KEYBOARD;
rect.size.height += kOFFSET_FOR_KEYBOARD;
self.view.frame = rect;
[UIView commitAnimations];
viewShifted = TRUE;
}
return YES;
}