iMessage Style Receding Keyboard in an iOS App - iphone

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

Related

Move view when jumping from text field to text field

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.

scroll textfield covered by keyboard

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.

Keyboard "WillShow" and "WillHide" vs. Rotation

I've got a view controller listening for both UIKeyboardWillShowNotification and UIKeyboardWillHideNotification. The handlers for these notifications adjust various parts of the view, which is standard procedure.
The following code is used to convert the keyboard rect from screen coordinates:
CGRect keyboardBounds = [self.view convertRect:[keyboardBoundsValue CGRectValue] fromView:nil];
Again, standard procedure. Unfortunately, there is a critical situation where this conversion fails. Look at what happens when an iPhone is rotated from portrait to landscape while the keyboard is deployed:
1) iOS automatically fires UIKeyboardWillHideNotification; self.interfaceOrientation is reported as portrait; keyboardBounds.height is 216.0. This makes sense. Why? Because the notification handler is given the chance to "clean up" before the view switches to landscape mode.
2) iOS automatically fires UIKeyboardWillShowNotification; self.interfaceOrientation is reported as portrait; keyboardBounds.height is 480.0. This does NOT make sense. Why not? Because the notification handler is going to do its work thinking that the height of the keyboard is 480.0!
Did Apple drop the ball on this one, or am I doing something wrong?
Please note that listening instead for UIKeyboardDidShowNotification is not a valid solution, because it significantly degrades the user experience. Why? Because animating my changes to the view after the keyboard deployment animation occurs is... well, pretty terrible-looking.
Has anyone managed to get autorotation working perfectly while the keyboard is deployed? It seems like an explosion of chaos that Apple has completely overlooked. >:|
Maybe a bit late, but I've just run into the same issue and have a nice solution for it that avoids any kind of work arounds (unless of course apple change things)
Basically, when the notification center calls your method for UIKeyboardWillShowNotification (or any of the other notifications), the frame that it gives you for UIKeyboardFrameBeginUserInfoKey is in context of the window, NOT your view. The problem with this, is that the windows coordinate system is always in portrait, regardless of the devices orientation, hence you're finding the width and height the wrong way round.
If you want to avoid your work around, simply convert the rectangle into the coordinate system of your view (which does change according to the orientation). To do this, do something like the following :
- (void) keyboardWillShow:(NSNotification *)aNotification
{
CGRect keyboardFrame = [[[aNotification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGRect convertedFrame = [self.view convertRect:keyboardFrame fromView:self.view.window];
......
/* Do whatever you want now with the new frame.
* The width and height will actually be correct now
*/
......
}
Hopefully this should be what you're after :)
Recently I've wrote a blog post about this exact problem you've described and how to solve it with a short and elegant way. Here is the link to the post: Synchronizing rotation animation between the keyboard and the attached view
If you don't want to dive into the long explanation described in the blog post here is a short description with a code example:
The basic principle is to use the same method that everyone uses - observing keyboard notifications to animate the attached view up and down. But in addition to that, you have to cancel these animations when the keyboard notifications are fired as a consequence of interface orientation change.
Rotation example without animation cancellation custom on interface orientation change:
Rotation example with animation cancellation on interface orientation change:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(adjustViewForKeyboardNotification:)
name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(adjustViewForKeyboardNotification:)
name:UIKeyboardWillHideNotification object:nil];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter]
removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter]
removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
self.animatingRotation = YES;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
self.animatingRotation = NO;
}
- (void)adjustViewForKeyboardNotification:(NSNotification *)notification {
NSDictionary *notificationInfo = [notification userInfo];
// Get the end frame of the keyboard in screen coordinates.
CGRect finalKeyboardFrame = [[notificationInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
// Convert the finalKeyboardFrame to view coordinates to take into account any rotation
// factors applied to the window’s contents as a result of interface orientation changes.
finalKeyboardFrame = [self.view convertRect:finalKeyboardFrame fromView:self.view.window];
// Calculate new position of the commentBar
CGRect commentBarFrame = self.commentBar.frame;
commentBarFrame.origin.y = finalKeyboardFrame.origin.y - commentBarFrame.size.height;
// Update tableView height.
CGRect tableViewFrame = self.tableView.frame;
tableViewFrame.size.height = commentBarFrame.origin.y;
if (!self.animatingRotation) {
// Get the animation curve and duration
UIViewAnimationCurve animationCurve = (UIViewAnimationCurve) [[notificationInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
NSTimeInterval animationDuration = [[notificationInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
// Animate view size synchronously with the appearance of the keyboard.
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:animationDuration];
[UIView setAnimationCurve:animationCurve];
[UIView setAnimationBeginsFromCurrentState:YES];
self.commentBar.frame = commentBarFrame;
self.tableView.frame = tableViewFrame;
[UIView commitAnimations];
} else {
self.commentBar.frame = commentBarFrame;
self.tableView.frame = tableViewFrame;
}
}
This answer was also posted in similar question: UIView atop the Keyboard similar to iMessage App
I met the same problem. iOS gaves me incorrect width/height of the keyboard. I used the following snipped in a keyboardDidShow handler:
CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGSize keyboardSize2 = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
LogDbg(#"keyboard size: frameBegin=%#; frameEnd=%#", NSStringFromCGSize(keyboardSize), NSStringFromCGSize(keyboardSize2));
and for portrait and landscape modes of iPad I got respectively:
2012-06-14 04:09:49.734 -[LoginViewController keyboardDidShow:] 132 [DBG]:keyboard size: frameBegin={768, 264}; frameEnd={768, 264}
2012-06-14 04:10:07.971 -[LoginViewController keyboardDidShow:] 132 [DBG]:keyboard size: frameBegin={352, 1024}; frameEnd={352, 1024}
Guessing that the width of the keyboard should be greater then the height (yep, i'm so naive) I made a workaround like following:
if (keyboardSize.width < keyboardSize.height)
{
// NOTE: fixing iOS bug: http://stackoverflow.com/questions/9746417/keyboard-willshow-and-willhide-vs-rotation
CGFloat height = keyboardSize.height;
keyboardSize.height = keyboardSize.width;
keyboardSize.width = height;
}
Well, try looking at keyboard width. If it is the value that you are expecting, then I assume that the values are simply switched ;). 480 makes sense as a keyboard width for going into landscape, which is what gives me this hunch.
If that fails, just store the portrait and landscape rectangles separately. They are well documented ;)
I know this a very very late reply. Now only I came on this situation and find the unanswered question. So I thought I'll share my solution. There will be some other better way, but the following way also we can solve this.
The KBKeyboardHandler that I used is from: UITextField: move view when keyboard appears
I just changed my delegate as following:
- (void)keyboardSizeChanged:(CGSize)delta
{
CGRect frame = self.view.frame;
UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
switch (interfaceOrientation) {
case UIInterfaceOrientationPortrait:
frame.origin.y-=delta.height;
break;
case UIInterfaceOrientationPortraitUpsideDown:
frame.origin.y+=delta.height;
break;
case UIInterfaceOrientationLandscapeLeft:
frame.origin.x-=delta.height;
break;
case UIInterfaceOrientationLandscapeRight:
frame.origin.x+=delta.height;
break;
default:
break;
}
self.view.frame = frame;
}
And it was working fine.
Here is my workaround:
CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
float keyboardHeight = self.interfaceOrientation == UIInterfaceOrientationPortrait ? keyboardSize.height : keyboardSize.width;
Hope this helps :)
I use the following code to get the size of the keyboard which works fine for all rotations
NSDictionary *info = [aNotification userInfo];
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
kbHeight = [[NSNumber numberWithFloat:[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.width] floatValue];
else
kbHeight = [[NSNumber numberWithFloat:[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height] floatValue];
NSLog(#"keyboard height = %F",kbHeight);
I then test for the orientation using the status bar orientation (which works in the first launch case for the iPad) and shift the view in the relative direction needed to make space for the keyboard. This works perfectly, if the keyboard is visible then it relocates to the correct position on rotations.
UIDeviceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if (orientation == UIDeviceOrientationPortrait)
{
NSLog(#"Orientation: portrait");
self.originalCenter = self.view.center;
self.view.center = CGPointMake(self.originalCenter.x, self.originalCenter.y-kbHeight);
}
if (orientation == UIDeviceOrientationPortraitUpsideDown)
{
NSLog(#"Orientation: portrait upside down");
self.originalCenter = self.view.center;
self.view.center = CGPointMake(self.originalCenter.x, self.originalCenter.y+kbHeight);
}
if (orientation == UIDeviceOrientationLandscapeLeft)
{
NSLog(#"Orientation: landscape left");
self.originalCenter = self.view.center;
self.view.center = CGPointMake(self.originalCenter.x+kbHeight,self.originalCenter.y);
}
if (orientation == UIDeviceOrientationLandscapeRight)
{
NSLog(#"Orientation: landscape right");
self.originalCenter = self.view.center;
self.view.center = CGPointMake(self.originalCenter.x-kbHeight,self.originalCenter.y);
}
You can return the view to its original position when the keyboard disappears or via a textFileDidEndEditing function.

Touch and pull down a view

I have a uiview at the top of the interface (below the status bar) that only the bottom part of it is shown.
Actually, I want to make the red uiview to slide down to be entirely shown by drag such as the notificationcenter in the native iOS and not just by taping a button.
What should I use to "touch and pull down" the uiview so it could be shown entirely ?
No needs to find a workaround of drag-n-drop. An UIScrollView can do it without any performance loss brought by listening on touches.
#interface PulldownView : UIScrollView
#end
#implementation PulldownView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (!self) {
return self;
}
self.pagingEnabled = YES;
self.bounces = NO;
self.showsVerticalScrollIndicator = NO;
[self setBackgroundColor:[UIColor clearColor]];
double pixelsOutside = 20;// How many pixels left outside.
self.contentSize = CGSizeMake(320, frame.size.height * 2 - pixelsOutside);
// redArea is the draggable area in red.
UIView *redArea = [[UIView alloc] initWithFrame:frame];
redArea.backgroundColor = [UIColor redColor];
[self addSubview:redArea];
return self;
}
// What this method does is to make sure that the user can only drag the view from inside the area in red.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (point.y > height)
{
// Leaving useless touches to the views under it.
return nil;
}
return [super hitTest:point withEvent:event];
}
#end
How to use:
1. Initialize an instance of PulldownView.
2. Add any content you want to display to the instance using [addSubview:].
3. Hide the area in red.
[pulldownView setContentOffset:CGPointMake(0, heightOfTheView - pixelsOutside)];
This is a simple example. You can add any features to it like adding a titled button bar on the bottom of the draggable area to implement click-n-drop, or adding some method to the interface to reposition it by the caller.
Make a subclass of UIView.
Override touchesBegan:withEvent and touchesMoved:withEvent.
In the touchesBegan perhaps make a visual change so the user knows they are touching the view.
In the touchesMoved use
[[touches anyObject] locationInView:self]
and
[[touches anyObject] previousLocationInView:self]
to calculate the difference between the current touch position and the last touch position (detect drag down or drag back up).
Then if you're custom drawing, call [self setNeedsDisplay] to tell your view to redraw in it's drawRect:(CGRect)rect method.
Note: this assumes multiple touch is not used by this view.
Refer to my answer in iPhone App: implementation of Drag and drop images in UIView
You just need to use TouchesBegin and TouchesEnded methods. In that example, I have shown how to use CGPoint, Instead of that you have to try to use setFrame or drawRect for your view.
As soon as TouchesMoved method is called you have to use setFrame or drawRect (not sure but which ever works, mostly setFrame) also take the height from CGPoint.

Pass Control (Responder?) back to Lower UIView whilst Still Performing Fade Out Animation on Upper UIView

From all of the view controllers within my application if I am processing a long running task I present the user a 'progress view'. This is a UIView that lives in my MainWindow.xib. I show (fade in) the view using an AppDelegate method...
- (void)showProgressView
{
self.progressView.hidden = NO;
[UIView animateWithDuration:1.0
animations:^(void) { self.progressView.alpha = 1.0; }];
}
When the long running task has finished I fade out the 'progress view' with the following AppDelegate method...
- (void)hideProgressView
{
[UIView animateWithDuration:1.0
animations:^(void) { self.progressView.alpha = 0.0; }
completion:^(BOOL f) { self.progressView.hidden = YES; }
}];
}
My problem is that as the progress view fades away and as the buttons/controls in the under-lying view below become visible again they ARE NOT usable (they don't respond to touch events) until the animation has fully finished and the 'progress view' is hidden.
Is there anyway for me to pass control back to the underlying view, before starting the fade out animation, so that its buttons etc do work whilst the progress view fades away?
EDIT: Things I have already tried unsuccessfully...
Using resignFirstResponder
- (BOOL)findAndResignFirstResponder:(UIView*)view
{
NSLog(#"looping %#", [view description]);
if (view.isFirstResponder)
{
[view resignFirstResponder];
return YES;
}
for (UIView *subView in view.subviews)
{
if ([self findAndResignFirstResponder: subView])
{
return YES;
}
}
return NO;
}
- (void)hideProgressView
{
// Recursively attempt to remove control from the progress view
BOOL result = [self findAndResignFirstResponder:self.progressView];
[UIView animateWithDuration:1.0
animations:^(void) { self.progressView.alpha = 0.0; }
completion:^(BOOL f) { self.progressView.hidden = YES; }
}];
}
Using endEditing
- (void)hideProgressView
{
// Attempt to remove control from the progress view
[self.progressView endEditing:YES];
[UIView animateWithDuration:1.0
animations:^(void) { self.progressView.alpha = 0.0; }
completion:^(BOOL f) { self.progressView.hidden = YES; }
}];
}
You can try sending your progress view the message resignFirstResponder. This should make the view below it the first responder, so you can use its controls.
PS: I also think that maybe your progress view could be filling the whole display; in this case, changing the first responder might not help...
EDIT: after you confirmed that your view is taking the full screen...
If your view is full screen, it is intercepting all the touches that you do (because when it is not fully hidden/transparent it is covering the views behind it). You have two options, either you make the view smaller, so that you have no overlapping, or you make so that the touches are forwarded to the view behind it.
You can do the latter in several ways, I hope that one works for you:
you can try and override in your progress view (it needs be a custom UIView), the touchesBegan method;
you can try and override the hitTest method in your progress view;
This is what I would try, e.g. in touchesBegan:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (![self viewIsDisappearing])
[self.nextResponder touchesBegan:touches withEvent:event];
}
viewIsDisappearing is a method you should implement to return YES if the animation to hide the progress view has already begun. During the animation the view is not yet hidden, so it will intercept touches, and you forward those touches to the next responder.
It is possible that you also need to override the other UIResponder's touche-related methods:
– touchesMoved:withEvent:
– touchesEnded:withEvent:
– touchesCancelled:withEvent:
EDIT:
I have found a class of mine where I do something similar to what I am suggesting here, only, without using the nextResponder.
The idea is: SDSTransparentView is a UIView that covers the whole screen. You initialize it like this:
[[SDSTransparentView alloc] initWithContent:contentView andDelegate:delegate];
The delegate implements an SDSTransparentViewProtocol which simply contains one method:
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event outsideOfView:(UIView*)view;
When the user touches anywhere on the transparent view, it relays the touch to the delegate by calling the protocol method. I would suggest you to ignore the contentView and outsideOfView arguments (they where useful for me, but possibly not for you; you can either pass nil or, better, the view behind the progress view).
You can find the class on my github. You only need the SDSTransparentView.* files. Actually, I only suggest having a look at how the class is implemented (very short) and do the same in your progress view.
I can assure that this approach works!