Animate UITextView resize using autolayout - ios

I'm implementing an autogrowing UITextView. I'm aiming for a similar behaviour of the message box in Whatsapp, which autogrows when your text has more than 1 line.
I'm using the approach described below which stores the height constraint in a UITextView subclass and modifies it when the text changes.
My solution animates correctly when I press the enter key inside the TextView, but it doesn't work when my typing goes over the end of the line. In this case it just changes size instantly.
Performing the animation on the delegate's - (void)textViewDidChange:(UITextView *)textView method produces the same result.
How can I correctly animate the TextView height using the auto layout system?
I'm implementing it like this:
#interface OEAutoGrowingTextView ()
#property (strong, nonatomic) NSLayoutConstraint *heightConstraint;
#end
#implementation OEAutoGrowingTextView
- (id)initWithFrame:(CGRect)frame
{
if ( !(self = [super initWithFrame:frame]) )
{
return nil;
}
[self commonInit];
return self;
}
- (void)awakeFromNib
{
[self commonInit];
}
- (void)commonInit
{
// If we are using auto layouts, than get a handler to the height constraint.
for (NSLayoutConstraint *constraint in self.constraints)
{
if (constraint.firstAttribute == NSLayoutAttributeHeight)
{
self.heightConstraint = constraint;
break;
}
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textDidChange:) name:UITextViewTextDidChangeNotification object:self];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)textDidChange:(NSNotification *)notification
{
self.heightConstraint.constant = self.contentSize.height;
[UIView animateWithDuration:1.0f animations:^
{
[self layoutIfNeeded];
}];
}
#end
Note: doing the following doesn't help.
- (void)textDidChange:(NSNotification *)notification
{
self.heightConstraint.constant = self.contentSize.height;
[UIView animateWithDuration:1.0f animations:^
{
[self layoutIfNeeded];
for (UIView *view in self.subviews)
{
[view layoutIfNeeded];
}
}];
}
Further update: This seems to be a bug in iOS 7.x, I think it's fixed on iOS 8.0.

I tried wrapping the heightConstraint change in a UIView animation block and that didn't work
That isn't how you animate a constraint change. You do it by changing the constraint and then animating the act of layout itself, like this:
// change the text view constraint here
[UIView animateWithDuration:duration animations:^{
[self.textView layoutIfNeeded];
}];

Ok, the issue is that as of ios7, .contentSize isn't correct for UITextViews. I have this functionality, and you need to compute the contentSize yourself. I added a category method to UITextView, -contentHeight, and use that instead to compute the contentSize.
See these two links.
UITextView Content Size
SO on the same question
Here is the code that fixes it:
#implementation UITextView (Sizing)
- (CGFloat)contentHeight {
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1) {
// This is the code for iOS 7. contentSize no longer returns the correct value, so
// we have to calculate it.
//
// This is partly borrowed from HPGrowingTextView, but I've replaced the
// magic fudge factors with the calculated values (having worked out where
// they came from)
CGRect frame = self.bounds;
// Take account of the padding added around the text.
UIEdgeInsets textContainerInsets = self.textContainerInset;
UIEdgeInsets contentInsets = self.contentInset;
CGFloat leftRightPadding = textContainerInsets.left + textContainerInsets.right + self.textContainer.lineFragmentPadding * 2;
leftRightPadding += contentInsets.left + contentInsets.right;
CGFloat topBottomPadding = textContainerInsets.top + textContainerInsets.bottom + contentInsets.top + contentInsets.bottom;
frame.size.width -= leftRightPadding;
frame.size.height -= topBottomPadding;
NSString* textToMeasure = self.text;
if(![textToMeasure isNotEmpty])
textToMeasure = #"-";
if ([textToMeasure hasSuffix:#"\n"]) {
textToMeasure = [NSString stringWithFormat:#"%#-", self.text];
}
// NSString class method: boundingRectWithSize:options:attributes:context is
// available only on ios7.0 sdk.
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
NSDictionary* attributes = #{NSFontAttributeName : self.font,
NSParagraphStyleAttributeName : paragraphStyle};
CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:attributes
context:nil];
CGFloat measuredHeight = ceilf(CGRectGetHeight(size) + topBottomPadding);
return measuredHeight;
} else {
return self.contentSize.height;
}
}
#end
Instead of contentSize, use this to compute the content height. You also don't need the animate at all - mine just computes and that is smooth enough, so you should make sure you really need the animation.

Related

UIView Animation Removes layer.cornerRadius After Completion

I have a UILabel that I am animating the constraints for so that it drop down into view. I am using layer.cornerRadius to give the view rounded corners, but for whatever reason after the animation completes the corner radius is removed.
[UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.7 initialSpringVelocity:0.4 options:UIViewAnimationOptionCurveEaseInOut animations:^{
if (shouldShow) {
self.labelOverMapTopConstraint.constant = 16;
} else {
self.labelOverMapTopConstraint.constant = -40;
}
[self.view layoutIfNeeded];
} completion:nil];
cornerRadius is set in viewDidLoad.
Is there a way to prevent this from happening?
I suspect you're subclassing UILabel here since it looks like you have padding in there, is that correct?
There could be something going awry with any custom drawing/calculations you're doing in there, so it would probably be helpful to post that code for inspection as well.
A few questions:
Do you have masksToBounds set to YES?
If you're not using a custom UILabel subclass, are you wrapping the label in a view?
How is the animation being triggered? Is it by a button? A callback from a NSURLRequest? If it's triggered by an async callback are you jumping back on the main queue to perform the animation?
If the animation is triggered automatically within the lifecycle, which lifecycle method is it triggered in?
I wasn't able to reproduce the issue in a test project with a vanilla UILabel. I then tried it with a UILabel subclass which includes additional padding and still wasn't able to reproduce it there.
I've included example code snippets below:
#import "ViewController.h"
#import "R4NInsetLabel.h"
#interface ViewController ()
#property BOOL showingToast;
#property (strong, nullable) IBOutlet R4NInsetLabel *toastLabel;
#property (strong, nullable) IBOutlet NSLayoutConstraint *toastLabelTopConstraint;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.navigationBar.titleTextAttributes = #{NSForegroundColorAttributeName : [UIColor whiteColor]};
self.showingToast = NO;
// start with the label pushed off the top of the screen
self.toastLabelTopConstraint.constant = -40.0f;
self.toastLabel.layer.cornerRadius = 6.0f;
self.toastLabel.layer.masksToBounds = YES;
}
- (IBAction)toggleToast:(id)sender {
[UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.7 initialSpringVelocity:0.4 options:UIViewAnimationOptionCurveEaseInOut animations:^{
if (self.showingToast == NO) {
self.toastLabelTopConstraint.constant = 16;
self.showingToast = YES;
} else {
self.toastLabelTopConstraint.constant = -40;
self.showingToast = NO;
}
[self.view layoutIfNeeded];
} completion:nil];
}
#end
#import "R4NInsetLabel.h"
IB_DESIGNABLE
#interface R4NInsetLabel()
#property IBInspectable CGFloat contentPadding;
#property (nonatomic) UIEdgeInsets contentInsets;
- (CGSize)_addInsetsToSize:(CGSize)size;
#end
#implementation R4NInsetLabel
- (UIEdgeInsets)contentInsets {
return UIEdgeInsetsMake(self.contentPadding, self.contentPadding, self.contentPadding, self.contentPadding);
}
- (CGSize)_addInsetsToSize:(CGSize)size {
CGFloat width = size.width + self.contentInsets.left + self.contentInsets.right;
CGFloat height = size.height + self.contentInsets.top + self.contentInsets.bottom;
return CGSizeMake(width, height);
}
- (void)drawTextInRect:(CGRect)rect {
CGRect insetRect = UIEdgeInsetsInsetRect(rect, self.contentInsets);
[super drawTextInRect:insetRect];
}
- (CGSize)intrinsicContentSize {
CGSize baseSize = [super intrinsicContentSize];
return [self _addInsetsToSize:baseSize];
}
- (CGSize)sizeThatFits:(CGSize)size {
CGSize baseSize = [super sizeThatFits:size];
return [self _addInsetsToSize:baseSize];
}
#end
And here's what it looks like:
You also need to set the corner radius on viewDidLayoutSubviews
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
label.layer.cornerRadius = yourValue
}

Autolayout : Resize view animation does not work

I am working now on custom UITextField, my main goal is to deliver custom placeholder animation. I want simply resize the placeholder and move it to top left corner. In gif bellow you can see move to top left works well, but resize is not animated and I have no idea way. Both of those actions are animated the same way with auto layout. Base on what I read it should works, and works for any other animation within auto layout, exclude view resize.
Thoughts / comments? What the heck am I doing wrong?
My current implementation:
#import "LTTextField.h"
#import "PureLayout.h"
#import <QuartzCore/QuartzCore.h>
#interface LTTextField()<UITextFieldDelegate>
#property (nonatomic, strong) UILabel *betterPlaceholder;
#property (nonatomic, strong) NSLayoutConstraint *heightConstraint;
#end
#implementation LTTextField
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self) {
[self setBorderStyle:UITextBorderStyleNone];
self.delegate = self;
self.betterPlaceholder = [[UILabel alloc] initForAutoLayout];
[self.betterPlaceholder setFont:[UIFont systemFontOfSize:17.0f]];
[self.betterPlaceholder setAdjustsFontSizeToFitWidth:YES];
[self.betterPlaceholder setBaselineAdjustment:UIBaselineAdjustmentAlignCenters];
[self addSubview:self.betterPlaceholder];
[self.betterPlaceholder autoPinEdge:ALEdgeLeading toEdge:ALEdgeLeading ofView:self];
[self.betterPlaceholder autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self];
[self.betterPlaceholder autoMatchDimension:ALDimensionHeight toDimension:ALDimensionWidth ofView:self.betterPlaceholder];
self.heightConstraint = [self.betterPlaceholder autoSetDimension:ALDimensionHeight toSize:CGRectGetHeight(self.frame)];
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
[self addTarget:self action:#selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
}
return self;
}
- (void)drawPlaceholderInRect:(CGRect)rect {}
- (void)awakeFromNib{
[super awakeFromNib];
[self refreshPlaceHolderText];
}
- (void)refreshPlaceHolderText{
if (self.placeholder) {
if (self.attributedPlaceholder) {
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedPlaceholder];
[attributedString setAttributes:#{NSForegroundColorAttributeName: [UIColor blueColor]} range:NSMakeRange(0, attributedString.string.length)];
[self.betterPlaceholder setAttributedText:attributedString];
} else {
[self.betterPlaceholder setText:self.placeholder];
}
NSLog(#"Placeholder text %#",self.placeholder);
}
}
- (void)animatePlaceholderToState:(LTPlaceholderState)state animated:(BOOL)animated {
if (LTPlaceholderStateStart == state) {
self.heightConstraint.constant = CGRectGetHeight(self.frame);
} else if (LTPlaceholderStateEnd == state) {
self.heightConstraint.constant = 20;
}
[UIView animateKeyframesWithDuration:1.0f
delay:0.0f
options:UIViewKeyframeAnimationOptionBeginFromCurrentState
animations:^{
[self setNeedsLayout];
[self layoutIfNeeded];
} completion:nil];
}
#pragma mark - UITextFieldDelegate
- (void)textFieldDidChange:(UITextField *)theTextField{
LTPlaceholderState placeholderState;
if( theTextField.text.length > 0 ) {
placeholderState = LTPlaceholderStateEnd;
} else {
placeholderState = LTPlaceholderStateStart;
}
[self animatePlaceholderToState:placeholderState animated:self.editing];
}
#end
It looks like your animation is working fine, and it's just that the font size snaps to the smaller font. You're forcing the width and height of the label to be the same with your use of autoMatchDimension, and you've requested setAdjustsFontSizeToFitWidth:YES. Ergo when the new layout is computed, the font size is reduced (or increased, depending on whether it's animating in or out). Font size is not an animatable property, so the change happens immediately.
You might have some luck in not changing the view frame height, but instead animating a change in both the scale and frame origin of the view. This will ensure it gets smaller, but doesn't actually require animating text attribute changes.

iOS 7.1 UITextView still not scrolling to cursor/caret after new line

Since iOS 7, a UITextView does not scroll automatically to the cursor as the user types text that flows to a new line. This issue is well documented on SO and elsewhere. For me, the issue is still present in iOS 7.1. What am I doing wrong?
I installed Xcode 5.1 and targeted iOS 7.1. I'm using Auto Layout.
Here's how I position the text view's content above the keyboard:
- (void)keyboardUp:(NSNotification *)notification
{
NSDictionary *info = [notification userInfo];
CGRect keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
keyboardRect = [self.view convertRect:keyboardRect fromView:nil];
UIEdgeInsets contentInset = self.textView.contentInset;
contentInset.bottom = keyboardRect.size.height;
self.textView.contentInset = contentInset;
}
What I have tried: I have tried many of the solutions posted to SO on this issue as it pertains to iOS 7. All of the solutions that I have tried do not seem to hold up well for text views displaying an attributed string. In the following three steps, I outline how the most up-voted answer on SO (https://stackoverflow.com/a/19277383/1239263) responds to the user tapping the return key for the first time.
(1.) The text view became the first responder in viewDidLoad. Scroll to the bottom of the text view where the cursor is located.
(2.) Before typing a single character, tap the return key on the keyboard. The caret disappears out of sight.
(3.) Tapping the return key again, however, seems to normalize the situation. (Note: deleting the latter new line, however, makes the caret disappear once again).
Improved solution's code for UITextView descendant class:
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define is_iOS7 SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0")
#define is_iOS8 SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0")
#implementation MyTextView {
BOOL settingText;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleTextViewDidChangeNotification:) name:UITextViewTextDidChangeNotification object:self];
}
return self;
}
- (void)scrollToCaretInTextView:(UITextView *)textView animated:(BOOL)animated {
CGRect rect = [textView caretRectForPosition:textView.selectedTextRange.end];
rect.size.height += textView.textContainerInset.bottom;
[textView scrollRectToVisible:rect animated:animated];
}
- (void)handleTextViewDidChangeNotification:(NSNotification *)notification {
if (notification.object == self && is_iOS7 && !is_iOS8 && !settingText) {
UITextView *textView = self;
if ([textView.text hasSuffix:#"\n"]) {
[CATransaction setCompletionBlock:^{
[self scrollToCaretInTextView:textView animated:NO];
}];
} else {
[self scrollToCaretInTextView:textView animated:NO];
}
}
}
- (void)setText:(NSString *)text {
settingText = YES;
[super setText:text];
settingText = NO;
}
Note it doesn't work when Down key is pressed on Bluetooth keyboard.
A robust solution should hold up in the following situations:
(1.) a text view displaying an attributed string
(2.) a new line created by tapping the return key on the keyboard
(3.) a new line created by typing text that overflows to the next line
(4.) copy and paste text
(5.) a new line created by tapping the return key for the first time (see the 3 steps in the OP)
(6.) device rotation
(7.) some case I can't think of that you will...
To satisfy these requirements in iOS 7.1, it seems as though it's still necessary to manually scroll to the caret.
It's common to see solutions that manually scroll to the caret when the text view delegate method textViewDidChange: is called. However, I found that this technique did not satisfy situation #5 above. Even a call to layoutIfNeeded before scrolling to the caret didn't help. Instead, I had to scroll to the caret inside a CATransaction completion block:
// this seems to satisfy all of the requirements listed aboveā€“if you are targeting iOS 7.1
- (void)textViewDidChange:(UITextView *)textView
{
if ([textView.text hasSuffix:#"\n"]) {
[CATransaction setCompletionBlock:^{
[self scrollToCaretInTextView:textView animated:NO];
}];
} else {
[self scrollToCaretInTextView:textView animated:NO];
}
}
Why does this work? I have no idea. You'll have to ask an Apple engineer.
For completeness, here's all of the code related to my solution:
#import "ViewController.h"
#interface ViewController () <UITextViewDelegate>
#property (weak, nonatomic) IBOutlet UITextView *textView; // full-screen
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *string = #"All work and no play makes Jack a dull boy.\n\nAll work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy.";
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string attributes:#{NSFontAttributeName: [UIFont fontWithName:#"Verdana" size:30.0]}];
self.textView.attributedText = attrString;
self.textView.delegate = self;
self.textView.backgroundColor = [UIColor yellowColor];
[self.textView becomeFirstResponder];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardIsUp:) name:UIKeyboardDidShowNotification object:nil];
}
// helper method
- (void)scrollToCaretInTextView:(UITextView *)textView animated:(BOOL)animated
{
CGRect rect = [textView caretRectForPosition:textView.selectedTextRange.end];
rect.size.height += textView.textContainerInset.bottom;
[textView scrollRectToVisible:rect animated:animated];
}
- (void)keyboardIsUp:(NSNotification *)notification
{
NSDictionary *info = [notification userInfo];
CGRect keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
keyboardRect = [self.view convertRect:keyboardRect fromView:nil];
UIEdgeInsets inset = self.textView.contentInset;
inset.bottom = keyboardRect.size.height;
self.textView.contentInset = inset;
self.textView.scrollIndicatorInsets = inset;
[self scrollToCaretInTextView:self.textView animated:YES];
}
- (void)textViewDidChange:(UITextView *)textView
{
if ([textView.text hasSuffix:#"\n"]) {
[CATransaction setCompletionBlock:^{
[self scrollToCaretInTextView:textView animated:NO];
}];
} else {
[self scrollToCaretInTextView:textView animated:NO];
}
}
#end
If you find a situation where this doesn't work, please let me know.
I solved it by getting the actual position of the caret and adjusting to it, here's my method:
- (void) alignTextView:(UITextView *)textView withAnimation:(BOOL)shouldAnimate {
// where the blinky caret is
CGRect caretRect = [textView caretRectForPosition:textView.selectedTextRange.start];
CGFloat offscreen = caretRect.origin.y + caretRect.size.height - (textView.contentOffset.y + textView.bounds.size.height - textView.contentInset.bottom - textView.contentInset.top);
CGPoint offsetP = textView.contentOffset;
offsetP.y += offscreen + 3; // 3 px -- margin puts caret 3 px above bottom
if (offsetP.y >= 0) {
if (shouldAnimate) {
[UIView animateWithDuration:0.2 animations:^{
[textView setContentOffset:offsetP];
}];
}
else {
[textView setContentOffset:offsetP];
}
}
}
If you only need to orient after the user presses return / enter, try:
- (void) textViewDidChange:(UITextView *)textView {
if ([textView.text hasSuffix:#"\n"]) {
[self alignTextView:textView withAnimation:NO];
}
}
Let me know if it works for you!
I can't find original source but it works on iOS7.1
- (void)textViewDidChangeSelection:(UITextView *)textView
{
if ([textView.text characterAtIndex:textView.text.length-1] != ' ') {
textView.text = [textView.text stringByAppendingString:#" "];
}
NSRange range0 = textView.selectedRange;
NSRange range = range0;
if (range0.location == textView.text.length) {
range = NSMakeRange(range0.location - 1, range0.length);
} else if (range0.length > 0 &&
range0.location + range0.length == textView.text.length) {
range = NSMakeRange(range0.location, range0.length - 1);
}
if (!NSEqualRanges(range, range0)) {
textView.selectedRange = range;
}
}
Some one have made a subclass that solves all scrolling0related issues in UITextView. The implementation couldn't be easier - switch UITextView with the subclass PSPDFTextView.
A post about it, showing what is fixed (With nice Gif animations) is here: Fixing UITextView on iOS 7
The git is here: PSPDFTextView

How to get a button to grow vertically using Auto Layout in iOS7?

Using IB and Auto Layout I would like to have a button that grows in size vertically when the title is long.
I've added top, leading and trailing constraint to the button and a Line break of Word wrap. Then I assign a long title in viewDidLoad. But the resulting button doesn't fit the content. What am I missing here?
Trying to overcome the lack of automated update of the height:
CGFloat width = 280;
NSAttributedString *attributedText =
[[NSAttributedString alloc]
initWithString:self.button.titleLabel.text
attributes:#
{NSFontAttributeName: self.button.titleLabel.font}];
CGRect rect = [attributedText boundingRectWithSize:(CGSize){width, CGFLOAT_MAX}
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
CGSize size = rect.size;
CGRect buttonRect = self.button.frame;
buttonRect.size.height = size.height;
[self.button setFrame: buttonRect];
But the frame of the button is not being updated.....
With the excellent solution provided by rdelmar below I got the following result:
If you want the title over the image (not beside) you need to use backgroundImage:forState:. As far as I can tell from experimenting with this, you can't get the image to expand with the title in an automatic way. If you have no image, then those constraints work to make the title expand vertically.
After Edit: I never found any good automatic way to do this, but you can do it by calculating the height of the string, and adjusting the height of the button accordingly. I found the best way to do this was to give the button both a width and height constraint in IB -- this will cause your background image to stretch or shrink to fit that frame. I made a UIButton subclass (changed the class of the the button in IB), and put this code in it:
#interface RDButton ()
#property (strong,nonatomic) NSLayoutConstraint *hCon;
#property (strong,nonatomic) NSLayoutConstraint *wCon;
#property (nonatomic) CGFloat startingHeight;
#end
#implementation RDButton
-(id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
for (NSLayoutConstraint *con in self.constraints) {
if (con.firstAttribute == NSLayoutAttributeHeight) {
self.hCon = con;
self.startingHeight = con.constant;
}else if (con.firstAttribute == NSLayoutAttributeWidth){
self.wCon = con;
}
}
}
self.titleLabel.numberOfLines = 0;
self.titleLabel.textAlignment = NSTextAlignmentCenter;
return self;
}
-(void)setTitle:(NSString *)title forState:(UIControlState)state {
[super setTitle:title forState:state];
NSStringDrawingContext *ctx = [NSStringDrawingContext new];
CGSize s = [title boundingRectWithSize:CGSizeMake(self.wCon.constant, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:self.titleLabel.font} context:ctx].size;
self.hCon.constant = fmax (s.height, self.startingHeight - 5) + 5; // the +5 gives about the same padding as the original button.
}
In the view controller where I'm setting the new title, I have this:
- (void)viewDidLoad {
[super viewDidLoad];
[self.button setTitle:#"A long long long Title" forState:UIControlStateNormal];
UIImage *img = [UIImage imageNamed:#"pic.jpg"];
[self.button setBackgroundImage:img forState:UIControlStateNormal];
}

UITextView inside UITableView doesn't scroll up when hit return key

I would like to create an app that has a function like iOS 7's Notes application. Basically there is one row on the top that has date and time.
My solution is to put the UITextView inside UITableView. First row is UILabel with date and time, the second row is UITextView.
I change both UITextView and UITableViewCell height according to UITextView ContentSize.
The problem is the UITextView size is large so it doesn't automatically scroll when the user hit return key.
Is there any solution to make it scroll as normal?
UITextView is a subclass of UIScrollView. I will suggest an alternative method of implementing a similar functionality. Add the label view as a subview of the text view, and set a contentInset top value of the height of the label.
UILabel* label = [UILabel new];
label.text = #"Test";
[label sizeToFit];
CGRect frame = label.frame;
frame.origin.y -= frame.size.height;
[label setFrame:frame];
[self.textView addSubview:label];
[self.textView setContentInset:UIEdgeInsetsMake(label.frame.size.height, 0, 0, 0)];
Sample project:
http://sdrv.ms/16JUlVD
Try this solution. Fix is based on inheritance. But logic can be used at any place after UITextView text was changed. I have taken some useful code blocks from here:
http://craigipedia.blogspot.ru/2013/09/last-lines-of-uitextview-may-scroll.html
and edited by me for my solution.
Should work.
#interface CustomTextView : UITextView
#end
#implementation CustomTextView
-(id)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textDidChange:) name:UITextViewTextDidChangeNotification object:self];
}
return self;
}
-(void)textDidChange:(NSNotification *)notification {
//iOS 7 UITextView auto scroll fix.
NSRange caretRange = self.selectedRange;
if (caretRange.location == self.text.length) {
CGRect textRect = [self.layoutManager usedRectForTextContainer:self.textContainer];
CGFloat sizeAdjustment = self.font.lineHeight * [UIScreen mainScreen].scale;
if (textRect.size.height >= self.frame.size.height - sizeAdjustment) {
if ([[self.text substringFromIndex:self.text.length - 1] isEqualToString:#"\n"]) {
[UIView animateWithDuration:0.2 animations:^{
[self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y + sizeAdjustment)];
}];
}
}
}
//end of fix
}
#end

Resources