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];
}
Related
I'm trying to animate UILabel's width. For some reason animation doesn't work and label changes it's size immediately. I use autolayout. Here is the code from the sample project I wrote to reproduce this. Tap on the button changes trailing constrains for both the button and the label.
#interface ViewController ()
#property (assign, nonatomic) BOOL controlsResized;
#property (weak, nonatomic) IBOutlet UIButton *button;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *buttonTrailingConstraint;
#property (weak, nonatomic) IBOutlet MyLabel *label;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *labelTrailingConstraint;
- (IBAction)doTapButton:(id)sender;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.button setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.label setTranslatesAutoresizingMaskIntoConstraints:NO];
self.label3.text = #"asdfasdfds sklfjfjls sdlkfj jjjjkfjkfdsjkdfsjklffsjkfdsjkl";
}
- (IBAction)doTapButton:(id)sender
{
if (!self.controlsResized)
{
[UIView animateWithDuration:0.5 animations:^{
self.buttonTrailingConstraint.constant = 0;
[self.button layoutIfNeeded];
self.labelTrailingConstraint.constant = 0;
[self.label layoutIfNeeded];
self.controlsResized = YES;
}];
}
else
{
[UIView animateWithDuration:0.5 animations:^{
self.buttonTrailingConstraint.constant = 100;
[self.button layoutIfNeeded];
self.labelTrailingConstraint.constant = 100;
[self.label layoutIfNeeded];
self.controlsResized = NO;
}];
}
}
#implementation MyLabel
- (void)drawTextInRect:(CGRect)rect
{
NSLog(#"drawTextInRect");
[super drawTextInRect:rect];
}
#end
As you can see on the video, it works for the button, but doesn't work for the label.
I tried to investigate and subclassed label from MyLabel class. It appears, that - (void)drawTextInRect:(CGRect)rect gets called immediately on the button tap, as a reason of [self.label layoutIfNeeded]; in the animation block. Why is it so? I would've expect the frame to be changed animated, as it is set inside the animation block. And it works as expected for the UIButton.
Why UILabel doesn't resize animated? Is there a way to fix it?
EDIT:
I tried the approaches suggested in current answers, but they don't work either. I think the animation code is not a problem here, because it works for the button in my example. The problem seems to be related to UILabel specific. UILabel seems to handle animation of size changes differently than other UIView subclasses, but I can't understand why.
I would post a comment but I do not have the necessary reputation for that. Here is what I would do:
self.buttonTrailingConstraint.constant = x;
self.labelTrailingConstraint.constant = x;
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];
When you update a constraint you need to take into account that the respective view's constraint you are modifying is not the only one that needs updating, so a layout method call is better made on the parent view.
This worked for me everytime. Hope this helps.
You must set constants of constraints before animate.
Try this :
- (IBAction)doTapButton:(id)sender
{
if (!self.controlsResized)
{
self.buttonTrailingConstraint.constant = 0;
self.labelTrailingConstraint.constant = 0;
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
self.controlsResized = YES;
}];
}
else
{
self.buttonTrailingConstraint.constant = 100;
self.labelTrailingConstraint.constant = 100;
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
self.controlsResized = NO;
}];
}
}
There are a couple parts to making this 100% work as expected:
Calling layoutIfNeeded first
Setting the constraint constant outside of the animation block
Calling layoutIfNeeded inside the block
Copied from a working implementation:
[self.view layoutIfNeeded];
self.topContainerConstraint.constant = constraintValue;
[UIView animateWithDuration:0.25 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished){ }];
I'd change your function to something like the following (obviously untested):
- (IBAction)doTapButton:(id)sender
{
if (!self.controlsResized)
{
[self.view layoutIfNeeded];
self.labelTrailingConstraint.constant = 0;
self.buttonTrailingConstraint.constant = 0;
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
self.controlsResized = YES;
}];
}
else
{
[self.view layoutIfNeeded];
self.labelTrailingConstraint.constant = 100;
self.buttonTrailingConstraint.constant = 100;
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
self.controlsResized = NO;
}];
}
}
I have a UIView subclass that contains a UITextField as subview. The idea is to animate this UIView and make the UITexField becomeFirstResponder when animation has ended. For some reason, after animation completion block has been executed and the UITextField has becomeFirstResponder, the UIView returns to its initial frame.
Here is the dummy sample code:
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIView *redView;
#property (strong, nonatomic) UITextField *textField;
#property (strong, nonatomic) UITapGestureRecognizer *gesture;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.textField = [[UITextField alloc] initWithFrame:CGRectMake(0.f, 0.f, self.redView.frame.size.width, 30.f)];
self.textField.backgroundColor = [UIColor lightGrayColor];
[self.redView addSubview:self.textField];
self.gesture = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(animateRedView:)];
[self.view addGestureRecognizer:self.gesture];
}
- (IBAction)animateRedView:(id)sender
{
[UIView animateWithDuration:.5
delay:0
usingSpringWithDamping:1.2
initialSpringVelocity:5
options:0
animations:^{
CGRect redViewFrame = self.redView.frame;
[self.redView setFrame:CGRectMake((self.view.frame.size.width - redViewFrame.size.width),
redViewFrame.origin.y,
redViewFrame.size.width,
redViewFrame.size.height)];
} completion:^(BOOL finished) {
[self.textField becomeFirstResponder];
}];
}
#end
Any idea?
Did you create redView in interface builder? If so it's going to have layout constraints attached to it.
You can remove them at runtime. Try adding this before you call setFrame
[self.redView removeConstraints:self.redView.constraints];
Also, you might try calculating the destination frame before the animation block (since you're calculating redFrame's new frame based on its current frame).
- (IBAction)animateRedView:(id)sender
{
CGRect redViewFrame = self.redView.frame;
CGRect newFrame = CGRectMake((self.view.frame.size.width - redViewFrame.size.width),
redViewFrame.origin.y,
redViewFrame.size.width,
redViewFrame.size.height);
[UIView animateWithDuration:.5
delay:0
usingSpringWithDamping:1.2
initialSpringVelocity:5
options:0
animations:^{
[self.redView setFrame:newFrame];
} completion:^(BOOL finished) {
[self.textField becomeFirstResponder];
}];
}
How do I customize a UIButton to behave like a table view cell?
I've tried make it in Interface Builder, but since the button is invisible and have no image, it's never gets highlighted when you press it. I want this grayed out behavior like on a table view cell.
Does anyone knows how to do it?
Simple way ever:
Make a 10x10px gray square png image, save it and name it like "greayButtBkg.png" and drag it into SupportingFiles in XCode.
Declare an Outlet of your button into your .h file and an IBAction method into your .m file.
yourFile.h:
#property (weak, nonatomic) IBOutlet UIButton *myButton;
yourFile.m:
- (IBAction)pressButton:(id)sender {
[_myButton setBackgroundImage:[UIImage imageNamed:#"greyButtBkg"] forState:UIControlStateHighlighted];
}
Your Button will get a grey background color when you'll tap it, and go back to its Default clearColor when you'll release your finger.
Hope this helps!
I have created a Custom Control that would behave like UITableViewCell. See code below
MyButton.h
#import <UIKit/UIKit.h>
#interface MyButton : UIView
- (id)initWithFrame:(CGRect)frame selector:(SEL)sel target:(id)target;
#end
MyButton.m
#import "MyButton.h"
#import <QuartzCore/QuartzCore.h>
#interface MyButton()
#property(assign)SEL selector;
#property(assign)id target;
#end
#implementation MyButton
- (id)initWithFrame:(CGRect)frame selector:(SEL)sel target:(id)target
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.selector=sel;
self.target=target;
self.backgroundColor=[UIColor darkGrayColor];
self.layer.cornerRadius=5.0;
UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapAction:)];
[self addGestureRecognizer:tapGesture];
}
return self;
}
-(void)tapAction:(id)sender{
[UIView animateWithDuration:.2 animations:^{
[self setBackgroundColor:[UIColor lightGrayColor]];
}];
[self performSelector:#selector(deSelect) withObject:nil afterDelay:.2];
}
-(void)deSelect{
[UIView animateWithDuration:.2 animations:^{
[self setBackgroundColor:[UIColor darkGrayColor]];
} completion:^(BOOL finished) {
[_target performSelector:_selector withObject:self];
}];
}
/*- (void)drawRect:(CGRect)rect
{
}*/
#end
Since, It uses UIView you can easily add, or draw anything on this control, and easily customize this.
Hope this helps.
Cheers.
In my viewController, there are two separate "custom views". And the first view has some quit animations, while the second view has some enter animations. The question is how could I change my view in my viewController exactly after the first view's quit animation is done? I have tried the view.hidden property, but it lost both the quit and the enter animations. I also tried to add some animateWithDuration thing and put the switchView method in the completion block, which turned out to fail, too.
Here is the code of my viewController:
#interface PopingViewController ()
#property (nonatomic, retain) HomepageView *homepage;
#property (nonatomic, retain) AboutView *about;
#end
#implementation PopingViewController
- (void)switchView
{
self.homepage = nil;
self.about = [[AboutView alloc] initWithFrame:self.view.frame];
self.about.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.about];
}
- (IBAction)tap:(UITapGestureRecognizer *)sender
{
// the onTouch method basically does some quit animations which take about 1 sec
[self.homepage onTouch:[sender locationInView:self.homepage]];
[self switchView];// it will execute before the animations are finished!
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.homepage = [[HomepageView alloc] initWithFrame:self.view.frame];
self.homepage.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.homepage];
}
Thanks in advance!
If they are just plain UIView subclasses then you could implement the UIView transition with class method transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion on UIView.
So, for your case, you would do it like this,
- (void)switchView
{
self.about = [[AboutView alloc] initWithFrame:self.view.frame];
[self.view insertSubView:about belowSubView:self.homepage];
self.about.backgroundColor = [UIColor whiteColor];
[UIView transitionFromView:self.homepage toView:self.about duration:0.6 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionShowHideTransitionViews completion:^(BOOL completed){ [self.homePage removeFromSuperView]; }];
}
Otherwise, if you are adding the UIViewController's view to some view then, you would use the UIViewController containment methods. Look at this for further detail http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html#//apple_ref/doc/uid/TP40007457-CH18-SW6
I have a method that drops down a UIView. That works fine. How would I go about having it slide back up when the button is touched a second time?
I also need to deactivate it somehow so that it doesn't repeat the animation if it's already displayed.
- (void)slideDownTableView {
self.tableViewCities = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
self.tableViewCities.frame = CGRectMake(0,0,300,180);
self.tableViewCities.autoresizingMask = ( UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth );
self.tableViewCities.dataSource = self;
self.tableViewCities.delegate = self;
self.tableViewCities.backgroundColor = [UIColor whiteColor];
UIView *myview=[[UIView alloc] initWithFrame: CGRectMake(10, 0,300,180)];
myview.backgroundColor=[UIColor redColor];
[myview addSubview:self.tableViewCities];
[self.view addSubview:myview];
myview.frame = CGRectMake(10, 0,300,-180); // offscreen
[UIView animateWithDuration:0.5
animations:^{
myview.frame = CGRectMake(10, 0,300,180); // final location move
}];
}
iOS provides autoreverse property by which animation reverses automatically.
UIViewAnimationOptionAutoreverse will give you an effect as the animation reverses .
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionAutoreverse
animations:^{
// do whatever animation you want, e.g.,
someView.frame = someFrame1;
}
completion:NULL];
Using BeginFromCurrentState option worked for me.
if(!enabled){
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationTransitionCurlUp
animations:^{
myview.frame = CGRectMake(10, 0,300,180);
}
completion:nil];
enabled=YES;
}
else{
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationTransitionCurlUp
animations:^{
myview.frame = CGRectMake(10, 0,300,-180);
}
completion:nil];
enabled=NO;
}
With a simple BOOL to store the current state of the sliding UIView you can achieve this.
The initial state is not visible if I understand well? So initially the BOOL citiesVisible is set to N0, then the button's action check its value, and performs a different animation accordingly. Simple.
Finally, I also disable the button during the animation to prevent the user to trigger a second animation while a first one is already performing.
I rewrote a bit your code to follow some best practices, and improve readability/maintainability:
Setting up the views beforehand because the button's action responsibility shouldn't be to setup the views, but only to animate them.
Changed the signature of the target action, that's my personal style guide to state in the sender's name and the ControlEvent, it makes it more tidy when you have multiple buttons/actions ;). Also per Apple guidelines, methods receiving actions should have one "id" parameter, the sender.
Store the frames in variables (you could also have them as #define, that'd be even better I think), so you don't have to hard-code them twice, it reduces the risk of breaking the animation by having a view sliding to different positions...
Code update:
#interface ViewController ()
#property (nonatomic, strong) UIButton *showCitiesButton;
#property (nonatomic, strong) CGRect citiesInitialFrame;
#property (nonatomic, strong) CGRect citiesVisibleFrame;
#property (nonatomic, strong) UITableView *tableViewCities;
#property (nonatomic, strong) UIView *citiesContainer;
#property (nonatomic, strong) BOOL citiesVisible;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Setting up the cities view
self.citiesInitialFrame = CGRectMake(10, -180, 300, 180);
self.citiesVisibleFrame = CGRectMake(10, 0, 300, 180);
self.tableViewCities = [[UITableView alloc] init ...];
self.citiesContainer = [[UIView alloc] init ...];
// ...
self.citiesVisible = NO;
[self.showCitiesButton addTarget:self
action:#selector(showCitiesButtonDidTouchUpInside:)
forControlEvents:UIControlEventTouchUpInside];
}
- (void)showCitiesButtonDidTouchUpInside:(id)sender
{
[self.showCitiesButton setEnabled:NO];
if (self.citiesVisible)
{
// Cities view is visible, sliding it out of the screen
[UIView animateWithDuration:0.5
animations:^{
self.citiesContainer.frame = self.citiesInitialFrame;
} completion:^(BOOL finished) {
self.citiesVisible = NO;
[self.showCitiesButton setEnabled:YES];
}];
}
else
{
// Cities view is not visible, sliding it in
[UIView animateWithDuration:0.5
animations:^{
self.citiesContainer.frame = self.citiesVisibleFrame;
} completion:^(BOOL finished) {
self.citiesVisible = YES;
[self.showCitiesButton setEnabled:YES];
}];
}
}
#end
You can reverse UIView animation with this code:
if (isMarked) {
[UIView animateWithDuration:3
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:(void (^)(void)) ^{
self.transform=CGAffineTransformMakeScale(1.1, 1.1);
}
completion:nil];
} else {
[UIView animateWithDuration:3
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:(void (^)(void)) ^{
self.transform=CGAffineTransformIdentity;
}
completion:nil];
}