Why moving parent view is not moving subview? - ios

I have a view as follow:
In my code I want to move this view keyboard size:
-(void) keyboardWillShow:(NSNotification *) notification {
NSDictionary* keyboardInfo = [notification userInfo];
// Work out where the keyboard will be
NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey];
CGRect keyboardFrameBeginRect = [keyboardFrameBegin CGRectValue];
// Work out animation duration
NSTimeInterval animationDuration =[[keyboardInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationOptions keyboardAnimationCurve = [[keyboardInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
// Animate this
[UIView animateWithDuration:animationDuration
delay:0.0
options:keyboardAnimationCurve
animations:^(){
self.view.frame = CGRectOffset(self.view.frame, 0, -keyboardFrameBeginRect.size.height);
}
completion:NULL];
}
It is moving everything to top except child views. Then I tried to move child views to the top as follow:
-(void) keyboardWillShow:(NSNotification *) notification {
NSDictionary* keyboardInfo = [notification userInfo];
// Work out where the keyboard will be
NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey];
CGRect keyboardFrameBeginRect = [keyboardFrameBegin CGRectValue];
// Work out animation duration
NSTimeInterval animationDuration =[[keyboardInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationOptions keyboardAnimationCurve = [[keyboardInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
// Animate this
[UIView animateWithDuration:animationDuration
delay:0.0
options:keyboardAnimationCurve
animations:^(){
self.view.frame = CGRectOffset(self.view.frame, 0, -keyboardFrameBeginRect.size.height);
self.memberNumberLayout.frame = CGRectOffset(self.memberNumberLayout.frame, 0, -keyboardFrameBeginRect.size.height);
}
completion:NULL];
}
self.memberNumberLayout is a subview of main UIView. But it is not moving. What could force sub view to stay there? Could it be because of constrains? As you can see to subviews and one label has not move with their parent view!
Constrains

While it is really hard to tell the problem from your constraints hierarchy screenshots, I think the problem is related to top constraints for self.memberNumberLayout my suggestions are the following:
1- use constrains instead of frames to animate your views by using IBOutlet from the top constraints of the container view then use the constant e.g self.containerConstraint.constant = keyboardSize * -1;
2- use scrollviews for such problems. UIScrollView is a great view for moving views up when keyboard appears or disappears, also this is can be very helpful when you run your app on different devices. Refer to this question for more information: How to make a UITextField move up when keyboard is present?

The problem is that you are saying self.view.frame =. You can't do that — this view is fixed as a subview of the window. Give self.view a content view the same size at itself (pinned to all four sides), with all other views inside it, and when the time comes, move the content view. Moreover, you've got constraints on these views (auto layout), so you must not change the frame of the content view; to move it, change its constraints.

Related

Why moving UIView is not moving UIImageView on same view?

I have an ImageView on a UIView. I'm trying to move view keyboard size but all content move properly instead of UIImageView.
-(void) keyboardWillShow:(NSNotification *) notification {
NSDictionary* keyboardInfo = [notification userInfo];
// Work out where the keyboard will be
NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey];
CGRect keyboardFrameBeginRect = [keyboardFrameBegin CGRectValue];
// Work out animation duration
NSTimeInterval animationDuration =[[keyboardInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationOptions keyboardAnimationCurve = [[keyboardInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
// Animate this
[UIView animateWithDuration:animationDuration
delay:0.0
options:keyboardAnimationCurve
animations:^(){
self.view.frame = CGRectOffset(self.view.frame, 0, -keyboardFrameBeginRect.size.height);
}
completion:NULL];
[UIImageView animateWithDuration:animationDuration
delay:0.0
options:keyboardAnimationCurve
animations:^(){
self.imgBankLogo.frame = CGRectOffset(self.imgBankLogo.frame, 0, -keyboardFrameBeginRect.size.height);
}
completion:NULL];
}
Anyone had same issue or some suggestion? I even tried to move UIImage but it is not moving.
As you can see everything moved except imageview!
If you are trying to move two views together, and one is a subview of the other, you shouldn't need to animate them individually.
Animating the parent view should move all of the subviews with it automatically. Trying to animate them individually at the same time can cause weird results in my experience. This animation block should be all you need. You may also want to check the Auto Layout settings in the views File Inspector tab.
If you want to perform more than one animation at once you can add multiple calls in the same animation block as well.
[UIView animateWithDuration:animationDuration
delay:0.0
options:keyboardAnimationCurve
animations:^(){
//you can add multiple here
self.view.frame = CGRectOffset(self.view.frame, 0, -keyboardFrameBeginRect.size.height);
}
completion:NULL];

How to make the scrollview scrollable in reduced view area when keyboard appears

I have embedded all my views in a UIScrollView from xib. The scrollview contents cover all screen below status bar. Now when the textfield is tapped, I am able to move the scrollview little up. But I want it to be completely scrollable till the bottom most view is also visible above the keyboard. Also when the scrollview is scrolled till top , it should come to normal original positions. Hence, Overall I want a completely scrollable functionality like mentioned above for my scrollview.
I am done with following tricks but with no luck:
Trick 1: Change the height of the scrollview so that the content is more than scrollview height and hence the view is scrollable:
-(void)keyboardWillAppear:(NSNotification *)sender
{
CGFloat y_offset=0;
if([UIScreen mainScreen].bounds.size.height == 480){
y_offset = 80;
} else {
y_offset = 70;
}
NSDictionary* userInfo = [sender userInfo];
CGRect keyboardEndFrame;
[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];
keyboardHeight = keyboardEndFrame.size.height;
[UIView animateWithDuration:0.5f animations:^{
[self.view setFrame:CGRectMake(0, - y_offset, self.view.frame.size.width, self.view.frame.size.height)];
}];
[self.loginScrollView setFrame:CGRectMake(self.loginScrollView.frame.origin.x, self.loginScrollView.frame.origin.y, self.loginScrollView.frame.size.width, [UIScreen mainScreen].bounds.size.height - keyboardHeight)];
}
-(void)keyboardWillDisappear:(NSNotification *)sender
{
[UIView animateWithDuration:0.5f animations:^{
[self.view setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
}];
[self.loginScrollView setFrame:CGRectMake(self.loginScrollView.frame.origin.x, self.loginScrollView.frame.origin.y, self.loginScrollView.frame.size.width, [UIScreen mainScreen].bounds.size.height)];
}
Trick 2: As per other suggestions, I changed the contentInset of the UIScrollView.
In keyboardWillAppear method I added following code:
CGSize kbSize = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height+100, 0.0);
self.loginScrollView.contentInset = contentInsets;
self.loginScrollView.scrollIndicatorInsets = contentInsets;
and in keyboardWillDisappear method I set the contentInset back to zero values.
Hence, let me know if there needs to be any other way to sort this out or any other possible changes I need to make in scrollview frame. Moreover , if I turn on the bouncesVertically functionality it is able to bounce even when complete subviews are visible onscreen which I don't want. So basically I want it to freeze when keyboard is not there and scrollable till viewable area when it is up. Hence, give me any other suggestions? Thanks in advance.
From a conceptual point of view when "scrollView Size == scrollView ContentSize", it does not scroll. To make it scrollable we need to increase the contentSize. In your problem you need to adjust the contentSize of scrollView along with frame. This can be done in your first approach.
As for the second approach, changing the edge insets will create a sort of padding for the content drawable area. This can make the bottom content visible, but it won't affect the contentSize, hence view will not be scrollable.
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
[self animateTextField:textField up:YES];
}
-(void)textFieldDidEndEditing:(UITextField *)textField
{
[self animateTextField:textField up:NO];
}
-(void)animateTextField:(UITextField*)textField up:(BOOL)up
{
const int movementDistance = -60; // change this size if you need
const float movementDuration = 0.3f; // change this size if you need
int movement = (up ? movementDistance : -movementDistance);
[UIView beginAnimations: #"animateTextField" context: nil];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration: movementDuration];
self.view.frame = CGRectOffset(self.view.frame, 0, movement);
[UIView commitAnimations];
}
it is useful for me
I can really recommend this library:
https://github.com/michaeltyson/TPKeyboardAvoiding
It's very very easy to use, and works for ScrollView, TableView and CollectionView!

Autolayout Issue with growing UITextView above keyboard

I have a UIViewController with a main view. Within that view I have a red UIView that has a UITextView inside, that I would like to grow vertically when text is written. I'm able to have this effectively done if the redView initially starts somewhere in the middle of the screen and doesn't move based on the keyboard popping up by just modifying the UITextViewHeightConstraint that I made.
-(void)textViewDidChange:(UITextView *)textView {
[self.textView setScrollEnabled:NO];
CGSize newSize = [textView sizeThatFits:CGSizeMake(textView.frame.size.width, 999)];
if (newSize.height > textView.frame.size.height) {
self.textViewHeight.constant = newSize.height;
}
}
When I dock the redView that includes the UITextView subview to the bottom of the screen, my code appropriately will bring the view above the keyboard nicely, using the following code:
-(void)adjustViewForKeyboardNotification:(NSNotification *)notification {
NSDictionary *notificationInfo = [notification userInfo];
CGRect finalKeyboardFrame = [[notificationInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
//Get AnimationCurve and AnimationDuration for keyboard popping up.
UIViewAnimationCurve animationCurve = (UIViewAnimationCurve) [[notificationInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
NSTimeInterval animationDuration = [[notificationInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
finalKeyboardFrame = [self.view convertRect:finalKeyboardFrame fromView:self.view.window];
CGRect textBarFrame = self.redView.frame;
textBarFrame.origin.y = finalKeyboardFrame.origin.y-textBarFrame.size.height;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:animationDuration];
[UIView setAnimationCurve:animationCurve];
[UIView setAnimationBeginsFromCurrentState:YES];
self.redView.frame = textBarFrame;
[UIView commitAnimations];
}
However, when I start typing and it comes time for the UITextView to expand, the redView is sent back down and is docked to the bottom of the screen (under the keyboard) - where it then expands vertically as I would have liked. I've tried adjusting the constraints in many different ways but haven't found a solution. I've tried setting the frame of the redView manually but that doesn't seem to work either (likely because of auto layout being enabled).
Any advice or tips would be greatly appreciated!

UITableView slightly goes up when keyboard hides

I am using UITableView (chatTable) along with UITabBar (chatTabBar) and one textField inside imageView. I am using autolayout. I used the following code to change the views when keyboard appears and disappears.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
// get animation info from userInfo
NSTimeInterval animationDuration;
CGRect keyboardFrame;
[[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] getValue:&keyboardFrame];
// resize the frame
[UIView animateWithDuration:animationDuration delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.keyboardHeight.constant = keyboardFrame.size.height - TABBAR_HEIGHT ;
[self.view layoutIfNeeded];
} completion:nil];
if ([chatData count] != VALUE_ZERO)
{
[chatTable scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([chatData count] - VALUE_ONE) inSection:VALUE_ZERO] atScrollPosition:UITableViewScrollPositionBottom animated:NO];
}
}
- (void)keyboardWillHide:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
// get animation info from userInfo
NSTimeInterval animationDuration;
CGRect keyboardFrame;
[[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] getValue:&keyboardFrame];
// Set view frame
[UIView animateWithDuration:animationDuration delay:2.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.keyboardHeight.constant -= keyboardFrame.size.height - TABBAR_HEIGHT;
[self.view layoutIfNeeded];
} completion:nil];
}
Now when I press return the tableview goes up a littel bit (from screen 2 to screen 3). keyboardHeight is the bottom space constraint between the tabBar and main view.
(screen 2)
(screen3)
I have tried many things but I can't able to find why the tableview is going up for a while. (problem is there is no smooth animation.) (Note: I have put delay as 2.0 only to show what happens in following screenshot(screen 3) othewise it's value would be 0)
Your problem is that you're changing the table view frame when the keyboard appears, which is wrong. You need to change the contentInset property of the table view, instead of meddling with frames.
- (void)keyboardWillShow:(NSNotification *)notification {
CGFloat height = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height - self.tabBarController.tabBar.frame.size.height;
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(0.0f, 0.0f, height, 0.0f);
_tableView.contentInset = edgeInsets;
_tableView.scrollIndicatorInsets = edgeInsets;
}
- (void)keyboardWillHide:(NSNotification *)notification {
UIEdgeInsets edgeInsets = UIEdgeInsetsZero;
_tableView.contentInset = edgeInsets;
_tableView.scrollIndicatorInsets = edgeInsets;
}
Solved the problem with contentInset property. I am using contentInset as mentiond by the #Eugene and also changing the constant property of bottom constraint of the textfiled to move up and doen whenever keyboard is shown and hidden.

How to test if keyboard is covering UITextField in UITableView?

How can I check if the keyboard is covering a first responder inside a UIScrollView which may or may not be a UITableView? Note the UIScrollView will not necessarily cover the entire viewController's view and may be contained in a modal view (UIModalPresentationFormSheet).
I'm using this modified code from Apple's reference documentation and example, but CGRectContainsPoint will return false even when the keyboard is clearly covering the first responder. It's obvious I'm not using convertRect:toView correctly.
Also, Apple's code does not take into account that the view is not full-screen, so setting the scrollView's contentInset to the full height of the keyboard isn't a great solution -- it should only be inset for the portion of the keyboard covering the firstResponder.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
// self.scrollView can be a tableView or not. need to handle both
UIView *firstResponderView = [self.scrollView findFirstResponder];
if (!firstResponderView)
return;
NSDictionary* info = [aNotification userInfo];
CGRect rect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
// convertRect:toView? convertRect:fromView? Always confusing
CGRect kbRect = [self.scrollView convertRect:rect toView:nil];
CGRect viewRect = [self.scrollView convertRect:firstResponderView.bounds toView:nil];
// doesn't work. convertRect misuse is certainly to blame
if (!CGRectContainsPoint(kbRect, firstResponderView.frame.origin))
return;
// Only inset to the portion which the keyboard covers?
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbRect.size.height, 0.0);
self.scrollView.contentInset = contentInsets;
self.scrollView.scrollIndicatorInsets = contentInsets;
}
Without further testing or having a deep look at the logic, this line seems odd:
CGRect kbRect = [self.scrollView convertRect:rect toView:nil];
The keyboard rect (that is included in the notification) is in window coordinates and you probably want to convert it into the scroll view coordinate system. [viewA convertRect:rect toView:viewB] converts rect from viewA's coordinate system to viewB's coordinate system, so you are actually doing the opposite of what you should be doing (as you suspected).
What I'm usually doing is this:
- (void)keyboardWillShow:(NSNotification *)aNotification
{
NSDictionary *info = [aNotification userInfo];
CGRect kbRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
kbRect = [self.view.window convertRect:kbRect toView:self.view]; // convert to local coordinate system, otherwise it is in window coordinates and does not consider interface orientation
_keyboardSize = kbRect.size; // store for later use
[UIView animateWithDuration:0.25 animations:^{
UIEdgeInsets insets = UIEdgeInsetsMake(0.0f, 0.0f, MAX(0.0f, CGRectGetMaxY(_tableView.frame) - CGRectGetMinY(kbRect)), 0.0f); // NB: _tableView is a direct subview of self.view, thus _tableView.frame and kbRect are in the same coordinate system
_tableView.contentInset = insets;
_tableView.scrollIndicatorInsets = insets;
[self scrollToActiveTextField]; // here I adapt the content offset to ensure that the active text field is fully visible
}];
}

Resources