6

Background

I'm working on a quick and dirty notes app purely to try to understand autolayout. As such I am looking for an autolayout-specific solution to this problem.

I am quite sure that my terminology and understanding of this subject may be incorrect in places so if I misphrase or omit information through ignorance that would otherwise be helpful I am very happy to update this question with better specifics.

Short Problem Summary

  • This app is a simple note app. On the detail view of the note, there are two text input views, a UITextField, and a UITextView.
  • The goal is to use autolayout to animate a change of height to the UITextView when it is being edited (making room for the keyboard), and then animate the UITextView back to it's original size when editing is finished.
  • The animation code I have in place works, however when the UITextView is scrolled near to the bottom of the text the animation from "editing" size to "non-editing" size displays incorrectly durring the animation. (The final result of the animation, however is correct.)
  • I'm open to alternate "correct" ways of doing this if there's a common pattern for the solution. I am, however, looking for an autolayout solution which, I believe, means avoiding modifying the view's frame directly. (Could be wrong on that.)

Details and Code

A short video of the problem is available here:
http://pile.cliffpruitt.com/m/constraint_problem.mp4

This is the code performing the animation:

// self.bodyFieldConstraintBottom refers to an outlet referencing the UITextView's bottom constraint

// This animation occurrs when the text view is tapped
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
    [self enterEditingMode];

    [UIView animateWithDuration:2.35
                     animations:^{
                         NSLayoutConstraint *bottom_constraint = self.bodyFieldConstraintBottom;
                         bottom_constraint.constant = 216;
                         [self.view layoutIfNeeded];
                     }];

    return YES;
}

// This animation occurrs when editing ends and the text field size is restored
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
    [self exitEditingMode];

    [UIView animateWithDuration:2.35
                     animations:^{
                         NSLayoutConstraint *bottom_constraint = self.bodyFieldConstraintBottom;
                         bottom_constraint.constant = 20;
                         [self.view layoutIfNeeded];
                     }];

    return YES;
}

Full project source (in all it's messy glory) can be downloaded here:
http://pile.cliffpruitt.com/dl/LittleNotebooks.zip

Additional Comments

My understanding of cocoa terminology isn't the best so I'm having a hard time making google searches and docs searches effective. My best guess about the problem (based on observing the animation at a slow speed) is that it is related to a scroll offset somehow because unless the text is scrolled past a certain point, the problem does not manifest itself.

I have read quite a few SO question/answers including:

The problem is that these answers either do not work ([self.bodyField setContentInset:UIEdgeInsetsMake(0, 0, 216, 0)]; seems to have no effect) or appear to rely on setting the frame of the UIText view which I believe is not supposed to be done when using autolayout.

Final Side Note

I've been at this off and on for about 4 days so my understanding and recollection of all I've read and tried is actually a little less clear than when I'd started. I hope I'm explaining this well enough to convey the issue.

EDIT:

I've noticed that this code actually gets somewhat close to the desired result:

- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
    [self enterEditingMode];


    [UIView animateWithDuration:2.35
                     animations:^{
                         [self.bodyField setContentInset:UIEdgeInsetsMake(0, 0, 216, 0)];
                         [self.view layoutIfNeeded];
                     }];

    return YES;
}

- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
    [self exitEditingMode];

    [UIView animateWithDuration:2.35
                     animations:^{
                         [self.bodyField setContentInset:UIEdgeInsetsMake(0, 0, 0, 0)];
                         [self.view layoutIfNeeded];
                     }];

    return YES;
}

The problem with this version is that the scroll indicator scrolls down past the visible area of the text content, meaning it gets "lost" behind the keybaord. Also, it does not help me understand the correct way to animate a UITextView (UIScrollView ?) bottom constraint.

Community
  • 1
  • 1
Cliff Pruitt
  • 201
  • 1
  • 4
  • 2
    If the second code is giving you the results you are looking for then the fix for the indicator is to set the scrollIndicatorInsets property of your scrollView as well. This will prevent it from going below the keyboard. – Justin Moser Aug 06 '14 at 18:24
  • 2
    You're almost there. Just set the `scrollIndicatorInsets` as well (200 works good for both of them - for your setup). If you want to get this even further, you could register for [keyboard notifications](https://developer.apple.com/library/ios/documentation/uikit/reference/UIWindow_Class/UIWindowClassReference/UIWindowClassReference.html) where you can grab the keyboard's frame and its animation duration/curve and scroll your textview to the bottom accordingly. Btw, if all questions were like this one, in terms of quality, the world would be a better place... Happy coding! – Alladinian Aug 06 '14 at 18:31
  • 1
    Thank you both for your comments. They are very helpful and do indeed work. (YAY!) Using this solution will require a few extra steps (such as scrolling the cursor position into view) but that's fine, I can look that stuff up. My only followup would be: Should I consider this an **appropriate** solution, or is it more of a workaround for a poorly animating constraint? Is it common to avoid animating constraints on UIScrollView and subclasses? I only ask for the clarification because the purpose of this app is specific to learning autolayout so I want to understand what I am coding and why. – Cliff Pruitt Aug 06 '14 at 18:50
  • strange, but for me your project worked as expected... (i had to fix a problem with NoteListViewController `viewDidLoad` preventing me to get to the detail view, but apart from that, it worked... – sergio Jan 16 '15 at 10:30

1 Answers1

0

The issue looks weird and I am really not sure whats the main issue but I found out that for the best results you should call [self.view setNeedsUpdateConstraints]; before animating view.

My example code to animate view when keyboard appears:

-(void)keyboardWillShow:(NSNotification *)notification {
    CGSize kbSize = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    //BH: iOS7 is messed up
    CGFloat keyboardHeight = kbSize.width;
    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
        keyboardHeight = kbSize.height;
    }

    self.centerYConstraint.constant = keyboardHeight;
    [self.view setNeedsUpdateConstraints];

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [self.view layoutIfNeeded];

    [UIView commitAnimations];
}

I am using commit animations to animate view with same animationCurve as iOS is animating keyboard so view is moving 1 to 1 accordingly to keyboard. Also, please notice if statement for iOS8 vs iOS7 where Apple finally fixed window sizing.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Bartosz Hernas
  • 1,130
  • 1
  • 9
  • 17