Move UIToolBar above keyboard on screen with UIScrollview - ios

I have a screen within an iPhone app that consist of a UITextView. This text view is contained within a UIScrollView. The purpose of the screen is for the user to type in text, and to optionally attach an image to what he is writing. Therefore, the screen also has a UIToolbar with a camera button at the bottom of the screen. The structure of the screen is as follows:
-View
--UIScrollView
---UITextView
--UIToolbar
---UIButton
When the user navigates to this screen, the viewDidAppear method assigns first responder to the uitextview element, so the keyboard shows up, which hides the toolbar and the camera button.
I would like the entire toolbar to re-draw itself right above the keyboard, and to position itself again at the bottom of the screen when the keyboard hides.
I have found related posts on SO (like this one). However, such methods introduce undesired behaviours. For example, implementing the solution in the article above, the toolbar does move with the keyboard, but the UIScrollView gets its frame.origin.y coordinate shifted way above the top of the screen, so it's impossible for the user to see what he is typing.
I have also tried to reset the frame of the toolbar, by adding it as an IBOutlet and using cgrectmake to reposition it. However, after several tries, the toolbar remains stuck at the bottom of the screen and hidden by the keyboard:
- (void) liftMainViewWhenKeybordAppears:(NSNotification*)aNotification{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.3];
[UIView setAnimationCurve:<#(UIViewAnimationCurve)#>]
CGRect frame = self.keyboardToolbar.frame;
frame.origin.y = self.keyboardToolbar.frame.origin.y - 280;
self.keyboardToolbar.frame = frame;
[UIView commitAnimations];
}
I have tried several iterations similar to the code above and they all fail at repositioning the toolbar.
So in short, what is the right way to float a toolbar right on top of a keyboard in a screen whose space is completely utilised by a uitextview element?

Thanks to RoryMcKinnel for the pointer. As the article referenced is in Swift, I thought I might paste the solution that worked for be on ObjC
- (void)keyboardWillShowNotification:(NSNotification *)notification{
NSDictionary *userInfo = notification.userInfo;
double animationDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGRect keyboardEndFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect convertedKeyboardFrame = [self.view convertRect:keyboardEndFrame fromView:self.view.window];
UIViewAnimationOptions rawAnimationCurve = (UIViewAnimationOptions)[userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue] << 16;
_toolBarBottomGuide.constant = CGRectGetMaxY(self.view.bounds) - CGRectGetMinY(convertedKeyboardFrame);
[UIView animateWithDuration:animationDuration delay:0.0 options:rawAnimationCurve animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
Bear in mind, this code did make the toolbar move as required, but the toolbar was not visible at all. It turned out that it was being hidden behind the UIScrollView. This was easily fixed by shifting the order between the scroll view and the toolbar element in the IB hierarchy.
The method above works for the keyboardWillShow event. You'll need to add the corresponding one for when the keyboard hides, like this:
- (void)keyboardWillHideNotification:(NSNotification *)notification{
NSDictionary *userInfo = notification.userInfo;
double animationDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGRect keyboardEndFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
UIViewAnimationOptions rawAnimationCurve = (UIViewAnimationOptions)[userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue] << 16;
_toolBarBottomGuide.constant = 0.0f;
[UIView animateWithDuration:animationDuration delay:0.0 options:rawAnimationCurve animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}

Related

Animating a UIButton to fullscreen

so I have a very simple button that when clicked goes to fullscreen and when clicked again goes back to the same position it was initially in. For some reason it works perfectly without the animation. When I uncomment the animation part when I initially click the button it does nothing, the second time I click it slightly enlarges. The third time I click it animates slowly but back to it's smaller original size... Why is it animating the opposite way?
- (IBAction)viewImage1:(id)sender {
UIButton *btn = (UIButton*) sender;
if (btn.tag == 0)
{
CGRect r = [[UIScreen mainScreen] bounds];
/*[UIView animateWithDuration:0.5f delay:0.0f options:0 animations:^{*/
[sender setFrame: r];
/*}completion:nil];*/
btn.tag = 1;
}
else
{
btn.tag = 0;
[sender setFrame:CGRectMake(0,0,370,200)];
}
}
There are two solutions to your problem either of which will work:
Disable Autolayout. (discouraged)
You can do that in Interface Builder by opening the File Inspector
in the right pane and unchecking the respective check box.
However, if you want to use Autolayout for constraining other UI elements in your view (which is quite a good idea in most cases) this approach won't work which is why I would recommend the second solution:
Keep Autolayout enabled, create an outlet for your button in your view controller and set
self.myButton.translatesAutoresizingMaskIntoConstraints = YES;
in your view controller's viewDidLoad method.
You could also add layout constraints to your button and animate those. (This excellent Stackoverflow post explains how it's done.)
The reason for this tricky behavior is that once you enable Autolayout a view's frame is no longer relevant to the actual layout that appears on screen, only the view's layout constraints matter. Setting its translatesAutoresizingMaskIntoConstraints property to YES causes the system to automatically create layout constraints for your view that will "emulate" the frame you set, in a manner of speaking.
It is easier to do this with auto layout and constraints. Create an IBOutlet for the height constraint of your button call it something like btnHeight. Do the same for the width constraint call it something like btnWidth. Then create an IBAction like so:
- (IBAction)buttonPress:(UIButton *)sender {
UIButton *btn = (UIButton *) sender;
CGRect r = [[UIScreen mainScreen] bounds];
if (CGRectEqualToRect(btn.frame, CGRectMake(0.0, 0.0, 370, 200))) {
[UIView animateWithDuration:0.5 delay:0.0 options:0 animations:^{
self.btnHeight.constant = r.size.height;
self.btnWidth.constant = r.size.width;
[self.view layoutIfNeeded];
} completion:^(BOOL finished){
}];
}else{
[UIView animateWithDuration:0.5 delay:0.0 options:0 animations:^{
self.btnHeight.constant = 200.0;
self.btnWidth.constant = 370.0;
[self.view layoutIfNeeded];
} completion:^(BOOL finished){
}];
}
}
In my experience animating the frame of a UIButton does not work well, a, the only method, I'm aware of, is to use CGAffineTransformScale which will rasterize the title of the button and scale it as well.

UITextField hidden while keyboard is showing

In my app I've this login screen:
Above the first test field I've an image, the configuration in Interface Builder is the follow:
Now when I tap on the UITextField they should move up otherwise in iPhone 4/4s the field will be covered by keyboard.
Now I'm using the following code:
-(void)textFieldDidBeginEditing:(UITextField *)textField {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.35f];
CGRect frameView = self.view.frame;
CGRect frameImage = self.imageViewLogo.frame;
CGRect frameTextFieldUsername = self.textFieldUsername.frame;
CGRect frameTextFieldPassword = self.textFieldPassword.frame;
CGRect frameButtonLogin = self.buttonLogin.frame;
frameView.origin.y = -100;
frameImage.origin.y = -100;
frameTextFieldUsername.origin.y = -100;
frameTextFieldPassword.origin.y = -100;
frameButtonLogin.origin.y = -100;
[self.view setFrame:frameView];
[self.imageViewLogo setFrame:frameImage];
[self.textFieldUsername setFrame:frameTextFieldUsername];
[self.textFieldPassword setFrame:frameTextFieldPassword];
[self.buttonLogin setFrame:frameButtonLogin];
[UIView commitAnimations];
}
When I try to run the app in simulator or on a real device the view scrolls up of 100 but the image, the text fields and button doesn't scrolls up... I thought that the problem depend on the constraints, can you help me to fix this issue?
Thank you
The way in which you are trying to do this will get you in trouble. As you are giving hardcoded values of frame.
Try to use keyboard avoiding library it's the better and safer way of doing it.
-(void)textFieldDidBeginEditing:(UITextField *)textField {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.35f];
CGRect frameView = self.view.frame;
CGRect frameImage = self.imageViewLogo.frame;
CGRect frameTextFieldUsername = self.textFieldUsername.frame;
CGRect frameTextFieldPassword = self.textFieldPassword.frame;
CGRect frameButtonLogin = self.buttonLogin.frame;
frameView.origin.y = -100;
// no need of changing frames of TextFields inside the View, Just change the y-padding of View
[self.view setFrame:frameView];
[UIView commitAnimations];
}
reduce the y-padding to -120 and check
Hope this helps thanks
If your views are set up to use AutoLayout, changing frame will do nothing.
You should subscribe to UIKeyboardWillChangeFrameNotification and perform your changes there. In case of constraints you would update the constrains constants here and the call -setNeedsUpdateConstraints on the superview.
You have to figure out if the keyboards is appearing or disappearing at all if you want to support external keyboard.
Otherwise you can listen for UIKeyboardWillShowNotification and UIKeyboardWillHideNotification
For auto up the textFields while keyboard comes not need to use the thirdParty Classes.
Create a static tableView and design each textFields and button in each static cells.
TableView automatically handle the auto up textFields while keyboard comes.
Hi the simple and easy trick for doing it is :-
Take outlet of top constraint of username or email id textfield in your class and on textfield did begin editing delegate decrease the constraint's constant value so it will goes up when u select the textfield and at textField did end editing delegate again set constraint's constant value to previous one so it will reset on previous position.
eg:-Assuming you set Top Constraint value in Storyboard is 150
-(void)textFieldDidBeginEditing:(UITextField *)textField {
_topConstraint.constant = 10.00;
[UIView animateWithDuration:0.5f animations:^{
[self.view layoutIfNeeded];
}];
}
-(void)textFieldDidEndEditing:(UITextField *)textField {
_topConstraint.constant = 150.00;
[UIView animateWithDuration:0.5f animations:^{
[self.view layoutIfNeeded];
}];
}
Hope it will help you.

Move UITextField up without moving whole view up

I have a UITableView inside a UIScrollView. The tableView does not span the length of the view as I have a UITextView at the bottom of the view (to enter messages) so the table ends at the textview. However, The scrollView does span the entire length of the view. When the keyboard comes up, I move the entire view up with it,(so nothing is hidden) but that makes the top cells of the table inaccessible (as in, I don't need to see the cells, but when i try to scroll to them I can't get to them) as it is pushing the whole view up. I am wondering if instead, I can push the scrollview underneath up, in turn having it push the keyboard up with it, and then have the tableview stay in place and scroll to the proper cell (for me in this case it is the most recent message, or last cell in the table). Having the tableview stay in place will allow scrolling access to all the cells in the table. Am I going about this the right way or is there an easier and better way to accomplish this? I'm working on a messaging app, and I want the UI to behave in a similar way to the native messages app. When the keyboard comes up, it brings the last message up above the top of the keyboard, but when the keyboard is visible you can still scroll to the top of the table. I will post my code for the movement of the view.
//call move view method and scroll to last cell in table
-(void) keyboardWasShown:(NSNotification*)aNotification
{
NSLog(#"Keyboard was shown");
[self moveView:[aNotification userInfo] up:YES];
[self.chatTable scrollRectToVisible:CGRectMake(0, self.chatTable.contentSize.height - self.chatTable.bounds.size.height, self.chatTable.bounds.size.width, self.chatTable.bounds.size.height) animated:YES];
}
-(void) keyboardWillHide:(NSNotification*)aNotification
{
NSLog(#"Keyboard will hide");
[self moveView:[aNotification userInfo] up:NO];
}
- (void)moveView:(NSDictionary*)userInfo up:(BOOL)up
{
CGRect keyboardEndFrame;
[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey]getValue:&keyboardEndFrame];
UIViewAnimationCurve animationCurve;
[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey]
getValue:&animationCurve];
NSTimeInterval animationDuration;
[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey]
getValue:&animationDuration];
// Get the correct keyboard size to we slide the right amount.
[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:animationDuration];
[UIView setAnimationCurve:animationCurve];
CGRect keyboardFrame = [self.view convertRect:keyboardEndFrame toView:nil];
int y = keyboardFrame.size.height * (up ? -1 : 1);
self.view.frame = CGRectOffset(self.view.frame, 0, y);
[UIView commitAnimations];
}
What I would do is NOT move the whole view up, but just shrink the scrollView's height by y amount.
So instead of
self.view.frame = CGRectOffset(self.view.frame, 0, y);
you could
CGRect scrlRect = self.myScrollView.frame;
self.myScrollView.frame = CGMakeRect(scrlRect.origin.x, scrlRect.origin.y, scrlRect.size.width, scrlRect.size.height+y);
The first step in dealing with this is modify the insets on the table. This gist would help you ensure that the tableView (or scrollview) scrollable area is no longer covered by the keyboard: https://gist.github.com/TimMedcalf/9505416
It may reveal further issues, but that's always my first port of call.
In your keyboarWasShown method wright below code:
- (void)keyboardWillShow:(NSNotification *)notification
{
NSDictionary *info = [notification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
float kbHeight = kbSize.width > kbSize.height ? kbSize.height : kbSize.width;
self.keyboardHeight.constant = kbHeight+5;
}
Now in your textFieldShouldBeginEditing delegate method of UITextField write below code:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
CGPoint point = [textField.superview convertPoint:textField.frame.origin toView:yourTableViewHere];
NSIndexPath *indexPath = [tblAsset indexPathForRowAtPoint:point];
CGRect tableViewVisibleRect = [tblAsset rectForRowAtIndexPath:indexPath];
if (point.y > (tableViewVisibleRect.size.height - self.keyboardHeight.constant))
{
CGPoint contentOffsetPoint = tblAsset.contentOffset;
contentOffsetPoint.y -= self.keyboardHeight.constant;
CGSize contectSize = tblAsset.contentSize;
contectSize.height += self.keyboardHeight.constant;
[tblAsset setContentSize:contectSize];
[tblAsset setContentOffset:contentOffsetPoint animated:NO];
}
}
Above is from my current project and its working somewhat like what you want. Using this way may help you achive your task.

iOS: How to adjust height of area covered by keyboard in iOS

Im curious about a feature in iOS. Please help me out here if you can.
Scenario: I am using a text box where name is entered. Its on lower half of the screen. Just below the text box is a label which displays the number of characters remaining(e.g.like in a twitter feed).
Problem: When i place the text box in upper half of the screen. both the text field and label are visible. But when I place them in lower half, the apple keyboard covers the label part.
Is there a way where I control the area covered in such a way that the label below is also visible?
I hope I have made myself clear enough.
Thanks.
Here i have used delegate method for UITextView Same way you can do for UITextField
-Here in this code when user starts entering values in textview it makes your view's hight lesser then its original with animation
-When user Ends Entering values, it will make your view's size original size.
-(void)textViewDidBeginEditing:(UITextView *)textView
{
CGRect frame = self.view.frame;
frame.origin.y = -100;
[self.view setFrame:frame];
[UIView commitAnimations];
}
-(void)textViewDidEndEditing:(UITextView *)textView
{
CGRect frame = self.view.frame;
frame.origin.y = 0;
[self.view setFrame:frame];
[UIView commitAnimations];
}
If you want to know about delegates this link helps you
Well in that case you have to move the textbox when the keyboard pops up.You can have the notification registered to know when the keyboard pops up and a scrollview to scroll the whole content up the screen can do the job for you
See this question,It explains well how to manage something like this
AFAIK You can't control the size of iOS native Keyboard, all you can and should be doing is, making them a subivew of a scroll view and scroll it up.
So the usual practice go something like this.
Subscribe to the Keyboard notification. UIKeyboardWillShowNotification
In the method which the Notification listener will be invoking, set the scrollView's content size accordingly and set the content offset.
self.scrollView.contentSize = CGSizeMake(320, 267);
self.scrollView.contentInset = UIEdgeInsetsMake(0, 0, localKeyboardFrame.size.height, 0);
[self.scrollView scrollRectToVisible:<rect of view you want to scroll to> animated:YES];
Undo the changes when the keyboard hides, with the help of appropriate notification.UIKeyboardWillHideNotification
self.scrollView.contentInset = UIEdgeInsetsZero
And here is iOS Human Interface Guide's explanation on it.
Add following to your viewDidLoad Method
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(showKeyboard) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(hideKeyboard) name:UIKeyboardWillHideNotification object:nil];
And after that-- Declare the following 2 methods in your .m file
-(void)showKeyboard {
[UIView beginAnimations:#"slide" context:nil];
[UIView setAnimationDuration:0.3];
self.view.transform = CGAffineTransformMakeTranslation(0, -100);
[UIView commitAnimations]; }
-(void)hideKeyboard {
[UIView beginAnimations:#"slide" context:nil];
[UIView setAnimationDuration:0.1];
self.view.transform = CGAffineTransformMakeTranslation(0, 0);
[UIView commitAnimations]; }

Weird positioning issue when tabBar is present

We have a custom view with a UIPickerView plus toolbar (216 + 44). At init time (viewDidLoad) this custom view is pushed below the screen using the following piece of code.
CGPoint newOrigin;
newOrigin.x = pickerViewOutlet.frame.size.width/2;
newOrigin.y = self.view.frame.size.height + ((pickerViewOutlet.frame.size.height)/2);
NSLog(#"%f,%f",self.view.frame.size.height,(pickerViewOutlet.frame.size.height)/2);
pickerViewOutlet.center = CGPointMake(newOrigin.x, newOrigin.y);
When a button is clicked this view is pulled up using the following piece of code.
[self.view bringSubviewToFront:pickerViewOutlet];
NSLog(#"tabbar %f",self.tabBarController.tabBar.frame.size.height);
CGPoint showOrigin;
showOrigin.x = pickerViewOutlet.frame.size.width/2;
showOrigin.y = pickerViewOutlet.center.y - pickerViewOutlet.frame.size.height;
//self.tabBarController.tabBar.frame.size.height ;
NSLog(#"showpicker %f,%f",pickerViewOutlet.center.y,pickerViewOutlet.frame.size.height);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.5];
pickerViewOutlet.center=CGPointMake(showOrigin.x, showOrigin.y);
[UIView commitAnimations];
[pickerCtrlOutlet reloadAllComponents];
This works fine. However this does not work (part of the view is below the tab bar) in the presence of a bottom tab bar controller on the page even though the code is modified as
showOrigin.y = pickerViewOutlet.center.y - pickerViewOutlet.frame.size.height - self.tabBarController.tabBar.frame.size.height ;
However if the above code is modified to
showOrigin.y = pickerViewOutlet.center.y - pickerViewOutlet.frame.size.height - self.tabBarController.tabBar.frame.size.height - 90;
it works perfectly where the view is right above the tab bar.
As far as I know, in viewDidLoad the self.view is not yet added to the superview and thus the frame is not set to the correct sizes.
For example, you can design a UIView in InterfaceBuilder and it will have 320x460. When you add it to the superview it will actually become smaller because of the bottom tab bar. The auto-resizing mechanism helps in this matter.
So, I think you are positioning the picker view using the wrong values in viewDidLoad and then when you use a new position relative to its old one, it will still be wrong.
Here is how I would write this:
[self.view bringSubviewToFront:pickerViewOutlet];
NSLog(#"tabbar %f",self.tabBarController.tabBar.frame.size.height);
CGPoint showOrigin;
showOrigin.x = pickerViewOutlet.frame.size.width/2;
//Notice this line -----------
showOrigin.y = self.view.frame.size.height - pickerViewOutlet.frame.size.height / 2;
//self.tabBarController.tabBar.frame.size.height ;
NSLog(#"showpicker %f,%f",pickerViewOutlet.center.y,pickerViewOutlet.frame.size.height);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.5];
pickerViewOutlet.center=CGPointMake(showOrigin.x, showOrigin.y);
[UIView commitAnimations];
[pickerCtrlOutlet reloadAllComponents];
Notice showOrigin.y = self.view.frame.size.height - ...
And (for extra points :) you can set the autoresizing masks for the picker view to Flexible Top (or, lock the bottom coordinate). If you do this, even if you position the picker view in viewDidLoad and then the self.view resizes, the pickerview will also change it's position.

Resources