Autolayout : Resize view animation does not work - ios

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.

Related

Animate UITextView resize using autolayout

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.

UIView transitionWithView with UILabel loses rounded corners during transition

I currently have a UIView that has UILabels sitting on top of it.
These UILabels have rounded corners.
I have a button which I press, which then programmatically removes all these labels, using the UIView transitionWithView method.
However, during the transition, the rounded corners are lost.
Is it possible to maintain these rounded corners during the transition?
i.e. The corners should remain rounded BEFORE, DURING and AFTER the transition.
Here's some example code:
#interface ExampleViewController
#property (strong, nonatomic) UIView *view;
#property (strong, nonatomic) UILabel *myLabel;
#end
#implementation ExampleViewController
- (void) viewDidLOad
{
self.myLabel = [[UILabel alloc] initWithFrame:self.view.frame];
[self.myLabel setText:#"Example Label"];
[self.myLabel setCenter:CGPointMake(100,100)]; // position the label somewhere on the screen
[self.myLabel.layer setCornerRadius:5]; // set the corner radius
[self.myLabel.layer setMasksToBounds:YES]; // found this particular line on another stackoverflow thread (http://stackoverflow.com/questions/11604215/uiview-transitionwithview-discarding-layer-settings)
[self.myLabel setHidden:NO];
[self.myLabel setBackgroundColor:[UIColor orangeColor]];
[self.myLabel setNumberOfLines:0];
[self.myLabel sizeToFit];
[self.view addSubview:self.myLabel];
}
// user interaction
- (IBAction)labelOff:(id)sender
{
BOOL hidden = [self.myLabel isHidden];
[UIView transitionWithView:self.myLabel
duration:1
options:UIViewAnimationOptionTransitionCrossDissolve
animations:NULL
completion:NULL];
[self.myLabel setHidden:!hidden];
}
#end
I am using XCode 5 and iOS 7. Any help is really appreciated.
I'm not sure about how the corner radius is getting removed during transition. But here is an alternative method you can use which has the same end result.
- (IBAction)labelOff:(id)sender
{
[UIView animateWithDuration:1.0f
animations:^{
self.myLabel.alpha = !self.myLabel.alpha;
}];
}
I hope that, you need to empty the UILabel.text but the properties should be avail. right?, Then
BOOL hidden; // Declare under #interface
- (IBAction)labelOff:(id)sender
{
if(hidden){
hidden =false;
[self.myLabel setText:#""];
}
else{
hidden =true;
[self.myLabel setText:#"Example Label"];
}
[UIView transitionWithView:self.myLabel
duration:1
options:UIViewAnimationOptionTransitionCrossDissolve
animations:NULL
completion:NULL];
}

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

Gracefully hiding and showing views when using autolayout

I thought there might be a way to easily hide and show a button in a row using auto layout so that views could be automatically arranged neatly depending on which are visible.
For example, say I have two buttons that I always want centered in a frame:
// pseudo visual format code:
|-----[star][download]-----|
When I press download I want now to see three buttons: (pause is the download button relabelled; cancel is a previously hidden button)
|--[star][cancel][pause ]--|
I thought I could perhaps have all three buttons always present but perhaps override the width to make the view gracefully animate between states? I thought there might be a more semantic way to achieve the adding and removing of views from the auto layout structure. Any thoughts?
I've put together a small sample showing how this could be done using a custom UIView subclass. In the example below, I've used the AutoLayout framework from this answer and I'd recommend you do the same; it keeps the constraint code clean and legible.
The general approach is that you have to keep pointers to the key constraints that bind the trailing edge of the left-hand buttons to the leading edge of those to the right and then use those pointers to dynamically add/remove constraints. Generally, you don't want to do too much of that because performance will suffer, but a small amount in response to a user action is OK.
My view is declared thus:
#protocol TSDownloadViewDelegate;
#interface TSDownloadView : UIView
#property (strong, nonatomic) id<TSDownloadViewDelegate> delegate;
#end
#protocol TSDownloadViewDelegate <NSObject>
- (void) downloadStartedInDownloadView:(TSDownloadView*)downloadView;
- (void) downloadPausedInDownloadView:(TSDownloadView *)downloadView;
- (void) downloadCancelledInDownloadView:(TSDownloadView*)downloadView;
#end
And implemented like this:
#import "UIView+AutoLayout.h"
#import "TSDownloadView.h"
static const CGFloat kMargin = 20.0;
#interface TSDownloadView ()
// Our buttons
#property (strong, nonatomic) UIButton * starButton;
#property (strong, nonatomic) UIButton * cancelButton;
#property (strong, nonatomic) UIButton * downloadButton;
// State tracking
#property (nonatomic) BOOL downloading;
#property (nonatomic) BOOL constraintsUpdated;
// The constraint governing what's tied to the right hand side of the starButton
#property (weak, nonatomic) NSLayoutConstraint *starRightConstraint;
// The constraint governing what's tied to the left hand side of the downloadButton
#property (weak, nonatomic) NSLayoutConstraint *downloadLeftConstraint;
#end
#implementation TSDownloadView
- (void) initializator
{
_starButton = [UIButton buttonWithType:UIButtonTypeSystem];
_cancelButton = [UIButton buttonWithType:UIButtonTypeSystem];
_downloadButton = [UIButton buttonWithType:UIButtonTypeSystem];
_starButton.translatesAutoresizingMaskIntoConstraints = NO;
_cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
_downloadButton.translatesAutoresizingMaskIntoConstraints = NO;
_starButton.titleLabel.textAlignment = NSTextAlignmentCenter;
_cancelButton.titleLabel.textAlignment = NSTextAlignmentCenter;
_downloadButton.titleLabel.textAlignment = NSTextAlignmentCenter;
[_starButton setTitle:#"Star" forState:UIControlStateNormal];
[_cancelButton setTitle:#"Cancel" forState:UIControlStateNormal];
[_downloadButton setTitle:#"Download" forState:UIControlStateNormal];
[_downloadButton addTarget:self action:#selector(downloadClicked:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_starButton];
[self addSubview:_cancelButton];
[self addSubview:_downloadButton];
_cancelButton.hidden = YES;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self initializator];
}
return self;
}
- (id) initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if( self )
{
[self initializator];
}
return self;
}
- (void)downloadClicked:(id)sender
{
self.downloading = !self.downloading;
if( self.downloading )
{
[self.downloadButton setTitle:#"Pause" forState:UIControlStateNormal];
self.cancelButton.hidden = NO;
// Remove previous constraints
[self removeConstraint:self.starRightConstraint];
[self removeConstraint:self.downloadLeftConstraint];
// |--[star][cancel][pause ]--|
self.starRightConstraint = [self.starButton autoPinEdge:ALEdgeRight toEdge:ALEdgeLeft ofView:self.cancelButton withOffset:-kMargin];
self.downloadLeftConstraint = [self.downloadButton autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:self.cancelButton withOffset:kMargin];
// Tell delegate what's happened
if( self.delegate )
[self.delegate downloadStartedInDownloadView:self];
}
else
{
[self.downloadButton setTitle:#"Download" forState:UIControlStateNormal];
self.cancelButton.hidden = YES;
// Remove previous constraints
[self removeConstraint:self.starRightConstraint];
[self removeConstraint:self.downloadLeftConstraint];
// |-----[star][download]-----|
self.starRightConstraint = [self.starButton autoPinEdge:ALEdgeRight toEdge:ALEdgeLeft ofView:self.downloadButton withOffset:-kMargin];
self.downloadLeftConstraint = nil;
// Tell delegate what's happened
if( self.delegate )
[self.delegate downloadPausedInDownloadView:self];
}
}
- (void) updateConstraints
{
[super updateConstraints];
if( self.constraintsUpdated ) return;
self.constraintsUpdated = YES;
// Now put our constraints in place
// Make sure the button hugs the label and doesn't get stretched
// just because there's space available
[self.starButton setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
// Pin the starButton to the top, left and bottom edges of its superview
[self.starButton autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:kMargin];
[self.starButton autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:kMargin];
[self.starButton autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:kMargin];
// Repeat for the other buttons
[self.cancelButton setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[self.cancelButton autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:kMargin];
[self.cancelButton autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:kMargin];
[self.downloadButton setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[self.downloadButton autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:kMargin];
[self.downloadButton autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:kMargin];
[self.downloadButton autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:kMargin];
// These two are special. We keep a reference to them so we can replace
// them later. Note that since the cancelButton is hidden at the start,
// the initial value for downloadLeftConstraint is simply nil.
self.starRightConstraint = [self.starButton autoPinEdge:ALEdgeRight toEdge:ALEdgeLeft ofView:self.downloadButton withOffset:-kMargin];
self.downloadLeftConstraint = nil;
}
#end
There's a lot more work to do to make the view really functional, but hopefully you can see the general approach to take.
Design the (5) buttons one over the other using Autolayout.
//on ViewDidLoad: set cancel & pause button to hide
-(void) viewDidLoad
{
[_pauseBtn setHidden:YES];
[_cancelBtn setHidden:YES];
}
//on Downlaod action
-(IBAction) downloadClick (UIButton *) sender
{
[_pauseBtn setHidden:NO];
[_cancelBtn setHidden:NO];
[sender setHidden:YES];
}
this can only be achieve handling constraints from code:
http://www.techotopia.com/index.php/Implementing_iOS_6_Auto_Layout_Constraints_in_Code

Displaying a single string in a view programmatically on iOS

We have a window filled with little view squares (think of a Calculator).
For a specific view on the window we want display a single string in the view without using the Interface Builder to add the string.
We need to be able to change the string and have the view refresh.
How do we programmatically add a string to a view and show it?
Update:
Ok here is the code we have currently. Nothing special in the header file.
I suppose the real quandry is considering we can easily get the background color to change, why is it that our text is just not showing??
Both versions are in there, would be happy to get 'apples' or 'oranges' displaying.
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
bgString = #"orange";
UILabel* aLabel = [[UILabel alloc] init];
aLabel.text = #"apple";
self.textLabel = aLabel;
[aLabel release];
[self addSubview:textLabel];
}
return self;
}
- (void)drawRect:(CGRect)rect {
// Drawing code
[[UIColor yellowColor] setFill];
UIRectFill(rect);
[self drawStringCenteredIn:rect];
}
- (void)drawStringCenteredIn:(CGRect)r {
//CGSize strSize = [bgString size];
CGPoint strOrigin;
strOrigin.x = r.origin.x; //+ (r.size.width - 10)/2;
strOrigin.y = r.origin.y; //+ (r.size.height - 10)/2;
//[bgString drawAtPoint:strOrigin withFont:[UIFont fontWithName:#"Helvetica" size:10]];
[textLabel drawTextInRect:r];
}
In your view controller's .h:
#interface MyViewController
{
UILabel* label;
}
#property (nonatomic, retain) UILabel* label;
In your view controller's .m:
- (void)dealloc
{
[label release];
[super dealloc];
}
- (void)viewDidLoad
{
[super viewDidLoad];
UILabel* aLabel = [[UILabel alloc] init];
aLabel.text = #"Initial Text";
self.label = aLabel;
[aLabel release];
[self.view addSubview:aLabel];
}
- (void)viewDidUnload
{
[self.label removeFromSuperview];
self.label = nil;
}
// Call this when you need to update the label
- (void)updateLabel
{
self.label.text = #"Some updated text";
}
Did that from memory but it should work.
Try this:
UILabel* aLabel = [[UILabel alloc] initWithFrame:[self bounds]];
If you are creating the label manually, you need to set it's frame manually too.
Frame itself is size and position inside parent view(superview).
In my example i've set the frame of label to occupy the entire view. If you need your custom size you can use:
UILabel* aLabel = [[UILabel alloc] initWithFrame:CGRectMake(x,y,width,height)];
Where (x,y) - position of the top left corner of your label.
How about creating a UILabel and adding it to the view?
If you subclass the UIView, you can draw your string in the view's drawRect. This allows great flexibility in modifying the text, its appearance, and its placement (you can even animate it around, spin, rotate, etc.)
Call setNeedsDisplay on the view after you change your NSString. Then do an drawAtPoint:withFont: on the NSString when the drawRect is called.

Resources